为什么使用 mmap 和 madvise 顺序逐行读取大文件比 fgets 慢?

juh*_*nic 6 c mmap systems-programming

概述

我有一个程序受 IO 限制,正在尝试加快速度。使用 mmap 似乎是一个好主意,但与仅使用一系列 fgets 调用相比,它实际上降低了性能。

一些演示代码

我已经将演示压缩到了基本要素,对一个 800mb 的文件进行了测试,大约有 350 万行:

使用 fgets:

char buf[4096];
FILE * fp = fopen(argv[1], "r");

while(fgets(buf, 4096, fp) != 0) {
    // do stuff
}
fclose(fp);
return 0;
Run Code Online (Sandbox Code Playgroud)

800mb 文件的运行时:

[juhani@xtest tests]$ time ./readfile /r/40/13479/14960 

real    0m25.614s
user    0m0.192s
sys 0m0.124s
Run Code Online (Sandbox Code Playgroud)

mmap 版本:

struct stat finfo;
int fh, len;
char * mem;
char * row, *end;
if(stat(argv[1], &finfo) == -1) return 0;
if((fh = open(argv[1], O_RDONLY)) == -1) return 0;

mem = (char*)mmap(NULL, finfo.st_size, PROT_READ, MAP_SHARED, fh, 0);
if(mem == (char*)-1) return 0;
madvise(mem, finfo.st_size, POSIX_MADV_SEQUENTIAL);
row = mem;
while((end = strchr(row, '\n')) != 0) {
    // do stuff
    row = end + 1;
}
munmap(mem, finfo.st_size);
close(fh);
Run Code Online (Sandbox Code Playgroud)

运行时变化很大,但永远不会比 fgets 快:

[juhani@xtest tests]$ time ./readfile_map /r/40/13479/14960

real    0m28.891s
user    0m0.252s
sys 0m0.732s
[juhani@xtest tests]$ time ./readfile_map /r/40/13479/14960

real    0m42.605s
user    0m0.144s
sys 0m0.472s
Run Code Online (Sandbox Code Playgroud)

其他注意事项

  • 看着进程在顶部运行,memapped 版本在此过程中产生了几千个页面错误。
  • fgets 版本的 CPU 和内存使用率都非常低。

问题

  • 为什么会这样?仅仅是因为 fopen/fgets 实现的缓冲文件访问比使用 madvise POSIX_MADV_SEQUENTIAL 积极预取那个 mmap 更好吗?
  • 是否有其他方法可以使速度更快(除了即时压缩/解压缩以将 IO 负载转移到处理器)?查看同一文件上 'wc -l' 的运行时,我猜可能不是这种情况。

Jen*_*edt 8

POSIX_MADV_SEQUENTIAL 只是对系统的一个提示,可能会被特定的 POSIX 实现完全忽略。

您的两种解决方案之间的区别在于,mmap需要将文件完全映射到虚拟地址空间,而fgetsIO 完全在内核空间中完成,只需将页面复制到不会更改的缓冲区中。

这也有更多的重叠可能性,因为 IO 是由某个内核线程完成的。

您也许可以mmap通过让一个(或多个)独立线程读取每个页面的第一个字节来提高实现的感知性能。这个(或这些)线程将有所有页面错误,并且当您的应用程序线程到达特定页面时,它已经被加载。