我的任务很简单:在Linux上用C ++读取和解析一个大文件。有两种方法:
逐字节解析。
while(/*...*/) {
... = fgetc(...);
/* do something with the char */
}
缓冲区解析缓冲区。
while(/*...*/) {
char buffer[SOME_LARGE_NUMBER];
fread(buffer, SOME_LARGE_NUMBER, 1, ...);
/* parse the buffer */
}
现在,逐字节解析对我来说更容易(不检查缓冲区有多满等)。但是,我听说读大片更有效率。
哲学是什么? “最佳”缓冲是内核的任务,所以当我调用fgetc()
时它已被缓冲了吗?或者是否建议我处理它以获得最佳效率?
此外,除了所有哲学:Linux上的现实是什么?
答案 0 :(得分:11)
无论fgetc()
的性能或底层缓冲如何,为所需的每个字节调用一个函数,而不是有一个适当大小的缓冲区来迭代,这是内核无法帮助你的开销。
我为我的本地系统做了一些快速而肮脏的时间(显然是YMMV)。
我选择了一个~200k的文件,并对每个字节求和。我做了20000次,在使用fgetc()
阅读和使用fread()
阅读之间每1000个周期交替进行。我将每1000个循环计时为一个整体。我编译了一个发布版本,并启用了优化。
fgetc()
循环变体始终 45x 慢于fread()
循环。
在评论中提示后,我还比较了getc()
,并且还改变了stdio缓冲区。表现没有明显变化。
答案 1 :(得分:3)
stdio 缓冲区不是内核的一部分。它是用户空间的一部分。
但是,您可以使用setbuf影响该缓冲区的大小。当该缓冲区不够满时, stdio 库将通过发出 read 系统函数来填充它。
因此,使用 fgetc 或 fread 在内核和用户之间切换的条款无关紧要。
答案 2 :(得分:1)
无所谓,真的。即使从SSD中,I / O开销也使得缓冲所花费的时间相形见绌。当然,它现在是微秒而不是毫秒,但函数调用是以纳秒为单位测量的。
答案 3 :(得分: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++];
}
答案 4 :(得分:0)
stdio例程执行用户空间缓冲。当你调用getc,fgetc,fread时,它们从stdio用户空间缓冲区中获取数据。当缓冲区为空时,stdio将使用内核读取调用来获取更多数据。
设计文件系统的人都知道磁盘访问(主要是搜索)非常昂贵。因此,即使stdio使用512字节的块大小,文件系统也可能使用4 KB的块大小,内核将一次读取4 KB的文件。
通常内核会在读取后启动磁盘/网络请求。对于磁盘,如果它看到你按顺序读取文件,它将开始提前读取(在你要求之前获取块),这样数据就可以更快地获得。
内核也会将文件缓存在内存中。因此,如果您正在读取的文件适合内存,则在一次运行程序后,该文件将保留在内存中,直到内核决定最好缓存您正在引用的其他文件。
使用mmap不会获得内核预读的好处。