FILE *和文件描述符读/写性能

时间:2013-07-08 10:36:48

标签: c performance sockets file-io

使用FILE*file descriptor API处理本地磁盘文件二进制数据读写有什么性能影响?这两种方式都有优势吗?

在效果方面,fread()read()要好吗?他们的行为,缓存或系统资源使用方式有何不同?

在效果方面,fwrite()write()要好吗?他们的行为,缓存或系统资源使用方式有何不同?

3 个答案:

答案 0 :(得分:4)

readwrite是系统调用:因此它们在用户空间中没有缓冲。您在那里提交的所有内容都将直接进入内核。 底层文件系统可能具有内部缓冲,但这里最大的性能影响将来自每次调用时更改为内核空间。

freadfwrite是用户空间库调用,默认情况下是缓冲的。因此,这些将把你的访问组合在一起,使它们更快(理论上)。

自己尝试:read一次一个字节的文件,然后一次一个字节fread。后者应该快4000倍。

#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;
}

我的OS X上的返回:

Time used by reading 1MiB: 103855 user, 442698 system.
Time used by freading 1MiB: 20146 user, 256 system.

stdio函数只是围绕相应的系统调用包装优化代码。

以下是该程序的strace

getrusage(RUSAGE_SELF, {ru_utime={0, 0}, ru_stime={0, 0}, ...}) = 0
open("/dev/zero", O_RDONLY)             = 3

然后是1048576次

read(3, "\0", 1)                        = 1

其余的:

close(3)                                = 0
getrusage(RUSAGE_SELF, {ru_utime={0, 200000}, ru_stime={5, 460000}, ...}) = 0

这是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

现在256次:

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

请注意,虽然我是逐字节读取的,但stdio库一次只能读取一页文件内容。

其余的主要是解除分配:

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)                           = ?

答案 1 :(得分:2)

关于访问磁盘上的文件,答案是:它取决于。更高级别的功能可以启用缓冲,这可以减少物理I / O的数量,这意味着它可以减少实现的read() / write()调用的实际数量(fread()调用read()访问磁盘等。)。

因此,在启用缓冲的情况下,高级功能的优势在于,您通常可以看到更好的性能,而无需考虑您正在做什么。低级功能的优势在于,如果您知道应用程序将如何执行操作,则可以通过直接管理自己的缓冲来提高性能。

答案 2 :(得分:1)

fread / fwrite比读/写更快,我同意,但是:

1)如果要随机访问文件,则无法有效地使用fwrite / fread,并且大部分时间它们都可能导致性能下降。

2)如果文件正由另一个进程或线程共享,那么它将不会那么快并且无法使用,除非您每次写入文件时都使用flush()命令,在这种情况下,速度至少与写入命令相同。此外,不能使用fread命令,因为它使用其缓冲区来读取可能未更新的数据,或者如果它关心更新,则必须丢弃读取新数据的内容。

所以,这取决于。