Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 18 Nov 1997 21:20:08 +0000
From:      njs3@doc.ic.ac.uk (Niall Smart)
To:        undisclosed-recipients:;
Subject:   Re: Optimizing HD I/O What size to use?
Message-ID:  <E0xXv44-0000Ay-00@oak47.doc.ic.ac.uk>

next in thread | raw e-mail | index | archive | help
Francisco Reyes said:
> I am going to start working on a program which will be heavy on I/O.
> I was wondering what would be a good size to read/write at a time.
> 
> What is the minimun block size FreeBSD allocates? 4K? 8K? Is it HD
> dependent?

The default blocksize used by newfs(8) is 8192 bytes, your program could
use some multiple of this size.  (I checked the source in -current for
this, it doesn't seem to be documented in the man page)

However a better way to determine the optimum block size for I/O to a
file is to f?stat(2) it and look at the st_blksize member.  I've attached
a program which takes a list of filenames and prints out the optimum I/O
block size for them.

Heres some sample output:

[nsmart@caligula nsmart]$ uname -r
2.2.2-RELEASE
[nsmart@caligula nsmart]$ ./a.out / /usr/bin/xargs /tmp/fifo /proc .
/: (directory) 8192 bytes optimal blocksize
/usr/bin/xargs: (regular) 8192 bytes optimal blocksize
/tmp/fifo: (FIFO) 8192 bytes optimal blocksize
/proc: (directory) 4096 bytes optimal blocksize
.: (directory) 8192 bytes optimal blocksize

Interestingly enough (or not) Solaris returns 1092 bytes as the optimum
I/O blocksize on procfs, 5120 for a FIFO and 4096 for swapfs.

To reduce the amount of system calls necessary you should use some (integer)
multiple of the optimum I/O block size.

Here's some sample times for a program that reads from stdin and writes to
stdout in blocks whose size is specified on the command line:

[nsmart@caligula nsmart]$ dd if=/dev/urandom of=temp bs=1024 count=1024
1024+0 records in
1024+0 records out
1048576 bytes transferred in 3.326055 secs (315261 bytes/sec)
[nsmart@caligula nsmart]$ for i in 32768 16384 8192 4096 128 10 8 5 1; do
> rm -f temp2
> time ./a.out $i < temp > temp2
> done
        0.21 real         0.00 user         0.06 sys
        0.21 real         0.00 user         0.06 sys
        0.23 real         0.00 user         0.07 sys
        0.23 real         0.00 user         0.07 sys
        0.64 real         0.01 user         0.33 sys
        3.75 real         0.18 user         3.27 sys
        4.68 real         0.14 user         4.19 sys
        7.30 real         0.32 user         6.57 sys
       34.36 real         1.68 user        31.87 sys
       
You pay a big performance hit if your blocksize is too small.

There is an even faster way of performing I/O, mmap(2).  By using mmap()
you avoid the buffer copying that is implied in the read() and write()
functions.

Heres some sample times for the above program versus one which uses mmap():

[nsmart@caligula nsmart]$ for i in 1 2 3 4 5; do
> rm -f temp2
> time ./mmap_cp < temp 1<>temp2
> rm -f temp2
> time ./rw_cp 8192 < temp >temp2
> echo
> done
        0.26 real         0.13 user         0.07 sys
        1.04 real         0.00 user         0.32 sys

        0.21 real         0.13 user         0.06 sys
        1.00 real         0.00 user         0.36 sys
				
        0.21 real         0.10 user         0.10 sys
        1.18 real         0.00 user         0.33 sys
				
        0.21 real         0.11 user         0.08 sys
        0.98 real         0.01 user         0.28 sys
							
        0.21 real         0.12 user         0.08 sys
        1.07 real         0.00 user         0.37 sys

Evidently, mmap() rocks.

Source code is appended,

Regards,

Niall



==> common.c - some common stuff :) <== 
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

/*
 * Older systems don't have strerror, this version of it
 * will do for them.  You may have to modify or remove
 * the declarations of sys_nerr and sys_nerrlist
 */
#ifdef NEED_STRERROR
char* strerror(int errnum);

extern int      sys_nerr;
extern char*    sys_errlist[];

char* strerror(int errnum)
{
        if (errnum > 0 && errnum < sys_nerr)
                return sys_errlist[errnum];
        else    
                return "Unknown error";
}
#endif

/*
 * Some utility functions for printing error messages
*/

extern char*	prog_name;		/* argv[0] */

void sys_err(char* fmt, ...)
{
	int		e = errno;
	va_list		va;

	
	fprintf(stderr, "%s: ", prog_name);	
	
	va_start(va, fmt);
	vfprintf(stderr, fmt, va);
	va_end(va);

	fprintf(stderr, "%s\n", strerror(e));
}


void prog_err(char* fmt, ...)
{
	va_list		va;
	
	fprintf(stderr, "%s: ", prog_name);

	va_start(va, fmt);
	vfprintf(stderr, fmt, va);
	va_end(va);

	fputc('\n', stderr);
}

==> common.h - the interface to common.c <==
#ifndef __COMMON_H__
#define __COMMON_H__

#ifdef NEED_STRERROR
char* strerror(int errnum);
#endif

void sys_err(char* fmt, ...);
void prog_err(char* fmt, ...);

#endif

==> mmap_copy.c - copy stdin to stdout using mmap() <==
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include "common.h"

char*		prog_name;

int main(int argc, char** argv)
{
	struct stat	st;
	caddr_t		in_buf;
	caddr_t		out_buf;
	
	prog_name = argv[0];
	
	if (argc != 1) {
		prog_err("I don't take any arguments!");
		exit(1);
	}	

	if (fstat(STDIN_FILENO, &st) == -1) {
		sys_err("could not stat stdin: ");	
		exit(1);
	}

	if ((in_buf = mmap((caddr_t) 0, st.st_size, PROT_READ, MAP_SHARED, STDIN_FILENO, 0)) == MAP_FAILED) {
		sys_err("could not mmap stdin: ");
		exit(1);	
	}
	
	lseek(STDOUT_FILENO, st.st_size - 1, SEEK_SET);
	
	write(STDOUT_FILENO, "", 1);
	
	if ((out_buf = mmap((caddr_t) 0, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, STDOUT_FILENO, 0)) == MAP_FAILED) {
		sys_err("could not mmap stdout: ");
		exit(1);
	}
	
	memcpy(out_buf, in_buf, st.st_size);

	munmap(in_buf, st.st_size);
	munmap(out_buf, st.st_size);
	
	if (ftruncate(STDOUT_FILENO, st.st_size) == -1) {
		sys_err("could not truncate stdout: ");	
	}

	return 0;
}

==> st_blksize.c - print out the st_blksize info for a file <==
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "common.h"

/*
 * Some systems don't support AF_UNIX domain sockets
 */
#ifndef S_ISSOCK
#define S_ISSOCK(x)	0
#endif

char*	prog_name;

int main(int argc, char** argv)
{
	struct stat	st;
	char*		file_type;
	
	prog_name = argv[0];
	
	if (argc == 1) {
		fprintf(stderr, "Usage: %s filename [filename...]\n", prog_name);
		exit(1);
	}
	
	for(argv++; *argv != NULL; argv++) {
		if (stat(*argv, &st) == -1) {
			sys_err("%s: ", *argv);
		} else {
			file_type = NULL;
			if (S_ISDIR(st.st_mode)) 	file_type = "directory";
			if (S_ISCHR(st.st_mode)) 	file_type = "character special";
			if (S_ISBLK(st.st_mode)) 	file_type = "block special";
			if (S_ISREG(st.st_mode)) 	file_type = "regular";
			if (S_ISSOCK(st.st_mode)) 	file_type = "socket";
			if (S_ISFIFO(st.st_mode)) 	file_type = "FIFO";
			if (file_type == NULL) 		file_type = "unknown";
			printf("%s: (%s) %ld bytes optimal blocksize\n", \
					*argv, file_type, (long) st.st_blksize);
		}
	}

	return 0;
}

==> vary_blk.c - copy stdin to stdout using read/write using varying block sizes <==
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include "common.h"

char*		prog_name;

int main(int argc, char** argv)
{
	int		blk_size;
	void*		buf;
	int		b;

	prog_name = *argv;
	
	if (argc != 2) {
		fprintf(stderr, "Usage: %s blocksize\n", prog_name);
		exit(1);
	}
	
	if ((blk_size = atoi(argv[1])) <= 0) {
		prog_err("Invalid blocksize: %d (%s)", blk_size, argv[1]);
		exit(1);
	}
	
	buf = malloc(blk_size);
	
	while ((b = read(STDIN_FILENO, buf, blk_size)) > 0)
		if (write(STDOUT_FILENO, buf, b) < b) {
			sys_err("write: ");
			exit(1);
		}
	
	if (b == -1) {
		sys_err("read: ");
		exit(1);
	}
	
	return 0;
}



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?E0xXv44-0000Ay-00>