What are the performance implications of using FILE* or file descriptor APIs for handling local-disk file binary data reads and writes? Does either way have any advantages over the other?
Is either fread() or read() better than the other in terms of performance? How do they differ in their behavior, caching or system resources usage?
Is either fwrite() or write() better than the other in terms of performance? How do they differ in their behavior, caching or system resources usage?
read and write are system calls: thus they are unbuffered in user space. Everything you submit there will go directly into the kernel.
The underlying file system may have internal buffering, but the biggest performance impact here will come from changing into kernel space on each call.
fread and fwrite are userspace library calls and are by default buffered. Thus these will group together your accesses to make them faster (in theory).
Try it yourself: read from a file one byte at a time and then fread from it one byte at a time. The latter should be a good 4000 times faster.
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/resource.h>
int main() {
struct rusage usage_start, usage_end;
getrusage(RUSAGE_SELF, &usage_start);
int fd = open("/dev/zero", O_RDONLY);
int i = 0x400 * 0x400; // 1 MB
char c;
while (i--)
read(fd, &c, 1);
close(fd);
getrusage(RUSAGE_SELF, &usage_end);
printf("Time used by reading 1MiB: %zu user, %zu system.\n", ((usage_end.ru_utime.tv_sec - usage_start.ru_utime.tv_sec)* 1000000) + usage_end.ru_utime.tv_usec - usage_start.ru_utime.tv_usec, ((usage_end.ru_stime.tv_sec - usage_start.ru_stime.tv_sec)* 1000000) + usage_end.ru_stime.tv_usec - usage_start.ru_stime.tv_usec);
getrusage(RUSAGE_SELF, &usage_start);
FILE * fp = fopen("/dev/zero", "r");
i = 0x400 * 0x400; // 1 MB
while (i--)
fread(&c, 1, 1, fp);
fclose(fp);
getrusage(RUSAGE_SELF, &usage_end);
printf("Time used by freading 1MiB: %zu user, %zu system.\n", ((usage_end.ru_utime.tv_sec - usage_start.ru_utime.tv_sec)* 1000000) + usage_end.ru_utime.tv_usec - usage_start.ru_utime.tv_usec, ((usage_end.ru_stime.tv_sec - usage_start.ru_stime.tv_sec)* 1000000) + usage_end.ru_stime.tv_usec - usage_start.ru_stime.tv_usec);
return 0;
}
Returns on my OS X:
Time used by reading 1MiB: 103855 user, 442698 system.
Time used by freading 1MiB: 20146 user, 256 system.
The stdio functions just wrap optimisation code around the appropriate system calls.
Here is an strace of the program:
getrusage(RUSAGE_SELF, {ru_utime={0, 0}, ru_stime={0, 0}, ...}) = 0
open("/dev/zero", O_RDONLY) = 3
Then follows 1048576 times
read(3, "\0", 1) = 1
and the rest:
close(3) = 0
getrusage(RUSAGE_SELF, {ru_utime={0, 200000}, ru_stime={5, 460000}, ...}) = 0
This is part of fopen:
fstat(1, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2aaaaaaae000
getrusage(RUSAGE_SELF, {ru_utime={0, 200000}, ru_stime={5, 460000}, ...}) = 0
// ...
open("/dev/zero", O_RDONLY) = 3
fstat(3, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 5), ...}) = 0
ioctl(3, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fffffffb050) = -1 ENOTTY (Inappropriate ioctl for device)
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2aaaaaaaf000
Now 256 times:
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096
Notice that although I am reading byte by byte the stdio library is fetching the file contents one page at a time.
And the rest mostly deallocations:
close(3) = 0
munmap(0x2aaaaaaaf000, 4096) = 0
getrusage(RUSAGE_SELF, {ru_utime={0, 230000}, ru_stime={5, 460000}, ...}) = 0
write(1, "Time used by reading 1MiB: 20000"..., 106Time used by reading 1MiB: 200000 user, 5460000 system.
Time used by freading 1MiB: 30000 user, 0 system.
) = 106
exit_group(0) = ?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With