【问题标题】:FILE* and file descriptor read/write performanceFILE* 和文件描述符读/写性能
【发布时间】:2013-07-08 10:36:47
【问题描述】:

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

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

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

【问题讨论】:

  • 你试过比较它们吗?你的结果是什么?
  • 这听起来像是一个测试。
  • @nvoigt 我没有,因为有很多事情需要考虑,比如缓冲区大小、磁盘文件系统等。我想得到关于“一般”区别的详细答案这两个 API。

标签: c performance sockets file-io


【解决方案1】:

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

【讨论】:

  • 感谢您的努力。然而,这个测试是非常片面的,它只表明 fread 真的被缓冲了。这不是唯一的因素,尽管我承认这是一个重要的因素。
  • @Dariusz 除了用户空间缓冲和stdio 库的一些额外便利功能(f* 调用)之外,没有太大的功能差异。通常,除非您需要对文件描述符(主要是特定于操作系统的内容)进行非常具体的控制,否则始终建议使用 stdio 而不是系统调用。如果有底层文件系统缓冲,那么这两种方法都不能做任何事情,但 stdio 通常更优化。
  • 没有更多的回应,你的是迄今为止最详细的,所以我接受了。谢谢!
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    fread/fwrite 比 read/write 快,我同意,但是:

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

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

    所以,这取决于。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-05
      • 2023-03-17
      • 1970-01-01
      • 1970-01-01
      • 2019-04-13
      • 1970-01-01
      • 2019-06-20
      相关资源
      最近更新 更多