11

我的任务很简单:在 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 的现实是什么?

4

5 回答 5

11

无论性能或底层缓冲如何fgetc(),为您需要的每个字节调用一个函数,而不是拥有一个合适大小的缓冲区来迭代,都是内核无法帮助您解决的开销。

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

我选择了一个约 200k 的文件,并对每个字节求和。我这样做了 20000 次,每 1000 次循环在 read usingfgetc()和 reading using之间交替fread()。我将每 1000 个循环计时为一个块。我编译了一个发布版本,并打开了优化。

fgetc()循环变体始终比循环慢45 倍fread()

在评论中提示后,我还比较getc()了,并且还改变了 stdio 缓冲区。性能没有明显变化。

于 2012-12-22T13:15:31.167 回答
3

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

但是,您可以使用setbuf影响该缓冲区的大小。当该缓冲区不够满时,stdio库将通过发出read系统函数来填充它。

因此,使用fgetc或在内核和用户之间切换的条款无关紧要。

于 2012-12-22T13:23:15.983 回答
1

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

于 2012-12-22T13:23:26.603 回答
1

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++];
 }
于 2012-12-22T15:01:55.520 回答
0

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

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

通常内核在读取后会发起磁盘/网络请求。对于磁盘,如果它看到您按顺序读取文件,它将开始提前读取(在您请求之前获取块),以便更快地获得数据。

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

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

于 2012-12-22T14:27:33.450 回答