2

使用APIFILE*file descriptorAPI 处理本地磁盘文件二进制数据读取和写入对性能有何影响?任何一种方式都比另一种方式有什么优势吗?

在性能方面是否fread()read()另一个更好?它们在行为、缓存或系统资源使用方面有何不同?

在性能方面是否fwrite()write()另一个更好?它们在行为、缓存或系统资源使用方面有何不同?

4

3 回答 3

4

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

fread并且fwrite是用户空间库调用,默认情况下是缓冲的。因此,这些会将您的访问组合在一起以使它们更快(理论上)。

自己尝试一下: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)                           = ?
于 2013-07-08T10:41:34.730 回答
2

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

因此,启用缓冲后,高级函数的优势在于您通常会看到更好的性能,而无需考虑您在做什么。低级函数的优势在于,如果您知道您的应用程序将如何做事,您可以通过直接管理自己的缓冲来提高性能。

于 2013-07-08T10:39:51.413 回答
1

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

1) 如果要随机访问文件,则 fwrite/fread 无法如此有效地使用,并且大多数情况下它们可能会导致性能下降。

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

所以,这取决于。

于 2013-07-08T15:16:31.500 回答