问题 “最佳”IO缓冲 - 程序员或内核的任务?


我的任务很简单:在Linux上用C ++读取和解析一个大文件。有两种方法:

  1. 逐字节解析。

    while(/*...*/) {
            ... = fgetc(...);
            /* do something with the char */
    }
    
  2. 缓冲区解析缓冲区。

    while(/*...*/) {
            char buffer[SOME_LARGE_NUMBER];
            fread(buffer, SOME_LARGE_NUMBER, 1, ...);
            /* parse the buffer */
    }
    

现在,逐字节解析对我来说更容易(没有检查缓冲区的满载程度等)。但是,我听说读大片更有效率。

什么是哲学?是“最佳”缓冲内核的任务,因此在我调用时它已经被缓冲了 fgetc()?或者是否建议我处理它以获得最佳效率?

此外,除了所有哲学:Linux上的现实是什么?


4205
2017-12-22 13:11


起源

你也可以使用 mmap() 把所有阅读工作留给内核而不关心这个。 - fge
@fge:除此之外 mmap 仅适用于本地文件,不适用于远程文件系统上的文件。 - Fred Foo
顺便说一句, getc 可能比效率更高 fgetc。后者将导致函数调用开销。 - Fred Foo
“程序员或内核的任务?” - 好吧,内核程序员。
有多大? - brian beuning


答案:


无论性能或底层缓冲 fgetc(),为所需的每个字节调用一个函数,而不是有一个适当大小的缓冲区来迭代,这是内核无法帮助你的开销。

我为我的本地系统做了一些快速而肮脏的计时(显然是YMMV)。

我选择了一个~200k的文件,并对每个字节求和。我这样做了20000次,在阅读之间每隔1000个循环交替使用 fgetc() 和阅读使用 fread()。我将每1000个周期定为一个整体。我编译了一个发布版本,并启用了优化功能。

fgetc() 循环变体始终如一 45X 慢于 fread() 循环。

在评论中提示后,我也进行了比较 getc(),还可以改变stdio缓冲区。表现没有明显变化。


11
2017-12-22 13:15



如果 fgetc() 在这个“迭代”里面,它可以快速调用(也许内联),那么应该没有任何区别,对吧?我的意思是:如果 c=fgetc(...) 几乎一样快 c=buffer[i]  - 那我们很酷,不是吗? - Johannes
这可能是一两个假设。 - JasonD
哇,现在你的运行时测试是一个沉重的争论!谢谢。你介意粘贴代码吗,所以我可以看看我的g ++如何优化? - Johannes
也可能很有趣:你使用了哪种缓冲区大小? - Johannes
@Johannes尝试使用默认值(512本地),64k和200k。没有明显的区别。 - JasonD


STDIO 缓冲区不是内核的一部分。它是用户空间的一部分。

但是,您可以使用该缓冲区的大小 函数setbuf。当那个缓冲区不够满时 STDIO 图书馆将通过发布来填补它  系统功能。

所以使用起来并不重要 龟etc 要么 FREAD 它在内核和用户之间切换的条件。


3
2017-12-22 13:23



有趣!你说我可以使用 fgetc() 无后顾之忧。但是,我需要找到一个很好的价值 setbuf() 这里?不应该是内核 知道 它? :) - Johannes
@Johannes - 这与内核无关。 Stdio是用户空间中的一个库,用于从内核执行缓冲(因此减少了上下文切换)。至于缓冲区的大小 - 这取决于数据的性质。 - Ed Heal


没关系,真的。即使从SSD中,I / O开销也使得缓冲所花费的时间相形见绌。当然,它现在是微秒而不是毫秒,但函数调用以纳秒为单位。


1
2017-12-22 13:23





fgetc缓慢的原因不是函数调用的数量,而是系统调用的数量。 fgetc 通常被实现为 int fgetc(FILE *fp) { int ch; return (fread(&ch,1,1,fp)==1?ch:-1); }

尽管fread本身可以缓冲64k或1k,但系统调用开销与例如

 int fgetc_buffered(FILE *fp) {
     static int head=0,tail=0; 
     static unsigned char buffer[1024];
     if (head>tail) return buffer[tail++];
     tail=0;head=fread(buffer,1,1024,fp);
     if (head<=0) return -1;
     return buffer[tail++];
 }

1
2017-12-22 15:01





stdio例程执行用户空间缓冲。当你调用getc,fgetc,fread时,它们从stdio用户空间缓冲区中获取数据。当缓冲区为空时,stdio将使用内核读取调用来获取更多数据。

设计文件系统的人都知道磁盘访问(主要是搜索)非常昂贵。因此,即使stdio使用512字节的块大小,文件系统也可能使用4 KB的块大小,内核将一次读取4 KB的文件。

通常,内核会在读取后启动磁盘/网络请求。对于磁盘,如果它看到你按顺序读取文件,它将开始提前读取(在你要求之前获取块),这样数据就可以更快地获得。

内核也会将文件缓存在内存中。因此,如果您正在阅读的文件适合内存,则在一次运行程序后,文件将保留在内存中,直到内核确定最好缓存您正在引用的其他文件。

使用mmap不会获得内核预读的好处。


0
2017-12-22 14:27