我的任务很简单:在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上的现实是什么?
无论性能或底层缓冲 fgetc()
,为所需的每个字节调用一个函数,而不是有一个适当大小的缓冲区来迭代,这是内核无法帮助你的开销。
我为我的本地系统做了一些快速而肮脏的计时(显然是YMMV)。
我选择了一个~200k的文件,并对每个字节求和。我这样做了20000次,在阅读之间每隔1000个循环交替使用 fgetc()
和阅读使用 fread()
。我将每1000个周期定为一个整体。我编译了一个发布版本,并启用了优化功能。
该 fgetc()
循环变体始终如一 45X 慢于 fread()
循环。
在评论中提示后,我也进行了比较 getc()
,还可以改变stdio缓冲区。表现没有明显变化。
该 STDIO 缓冲区不是内核的一部分。它是用户空间的一部分。
但是,您可以使用该缓冲区的大小 函数setbuf。当那个缓冲区不够满时 STDIO 图书馆将通过发布来填补它 读 系统功能。
所以使用起来并不重要 龟etc 要么 FREAD 它在内核和用户之间切换的条件。
没关系,真的。即使从SSD中,I / O开销也使得缓冲所花费的时间相形见绌。当然,它现在是微秒而不是毫秒,但函数调用以纳秒为单位。
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++];
}
stdio例程执行用户空间缓冲。当你调用getc,fgetc,fread时,它们从stdio用户空间缓冲区中获取数据。当缓冲区为空时,stdio将使用内核读取调用来获取更多数据。
设计文件系统的人都知道磁盘访问(主要是搜索)非常昂贵。因此,即使stdio使用512字节的块大小,文件系统也可能使用4 KB的块大小,内核将一次读取4 KB的文件。
通常,内核会在读取后启动磁盘/网络请求。对于磁盘,如果它看到你按顺序读取文件,它将开始提前读取(在你要求之前获取块),这样数据就可以更快地获得。
内核也会将文件缓存在内存中。因此,如果您正在阅读的文件适合内存,则在一次运行程序后,文件将保留在内存中,直到内核确定最好缓存您正在引用的其他文件。
使用mmap不会获得内核预读的好处。