我正在开发一个程序,可以处理100GB或更大的文件。文件包含可变长度的记录集。我已经有了第一个实现并运行,现在正在寻求提高性能,特别是在更有效地做I/O,因为输入文件被扫描了很多次。
是否有使用mmap()与通过c++的fstream库读取块的经验法则?我想做的是将大块从磁盘读入缓冲区,处理缓冲区中的完整记录,然后读取更多数据。
mmap()代码可能会变得非常混乱,因为mmap的块需要位于页面大小的边界上(我的理解),而记录可能位于页面边界上。使用fstreams,我可以只寻找记录的开始并重新开始读取,因为我们不局限于读取位于页面大小边界上的块。
如果不首先编写完整的实现,我如何在这两个选项之间做出决定呢?有什么经验法则(例如,mmap()快2倍)或简单的测试吗?
我试图找到关于Linux上mmap / read性能的最后一句话,我在Linux内核邮件列表上看到了一个不错的帖子(链接)。从2000年开始,内核对IO和虚拟内存进行了很多改进,但这很好地解释了为什么mmap或read可能更快或更慢。
调用mmap的开销比读取的多(就像epoll比poll的开销多,poll的开销比读取的多)。在某些处理器上,更改虚拟内存映射是一项相当昂贵的操作,原因与在不同进程之间切换成本相同。
IO系统已经可以使用磁盘缓存,所以如果读取一个文件,无论使用什么方法,都将命中缓存或错过缓存。
然而,
Memory maps are generally faster for random access, especially if your access patterns are sparse and unpredictable.
Memory maps allow you to keep using pages from the cache until you are done. This means that if you use a file heavily for a long period of time, then close it and reopen it, the pages will still be cached. With read, your file may have been flushed from the cache ages ago. This does not apply if you use a file and immediately discard it. (If you try to mlock pages just to keep them in cache, you are trying to outsmart the disk cache and this kind of foolery rarely helps system performance).
Reading a file directly is very simple and fast.
关于mmap/read的讨论让我想起了另外两个性能的讨论:
一些Java程序员惊讶地发现,非阻塞I/O通常比阻塞I/O慢,如果您知道非阻塞I/O需要进行更多的系统调用,这是完全有道理的。
其他一些网络程序员惊讶地发现epoll通常比poll慢,如果您知道管理epoll需要进行更多的系统调用,那么这是完全有道理的。
结论:如果随机访问数据,长时间保存数据,或者知道可以与其他进程共享数据,请使用内存映射(如果没有实际的共享,MAP_SHARED就没有什么意义)。如果按顺序访问数据,则正常读取文件或在读取后丢弃文件。如果任何一种方法能让你的程序不那么复杂,那就这么做。对于许多真实世界的案例,如果不测试实际应用程序,而不是基准测试,就没有确定的方法来显示一个更快。
(对不起,我想问这个问题,但我一直在寻找答案,这个问题一直出现在谷歌结果的顶部。)
我同意mmap文件I/O将会更快,但是当您对代码进行基准测试时,不应该对反例进行一些优化吗?
本·柯林斯写道:
char data[0x1000];
std::ifstream in("file.bin");
while (in)
{
in.read(data, 0x1000);
// do something with data
}
我建议你也试试:
char data[0x1000];
std::ifstream iifle( "file.bin");
std::istream in( ifile.rdbuf() );
while( in )
{
in.read( data, 0x1000);
// do something with data
}
除此之外,您还可以尝试使缓冲区大小与一页虚拟内存大小相同,以防0x1000不是您机器上一页虚拟内存的大小……IMHO mmap文件I/O仍然是赢家,但这应该使事情更接近。
主要的性能成本是磁盘i/o。"mmap()"当然比istream快,但这种差异可能不明显,因为磁盘I / O将主导您的运行时。
我尝试了Ben Collins的代码片段(见上面/下面)来测试他的断言“mmap()快得多”,并没有发现可测量的差异。请看我对他的回答的评论。
我当然不建议逐个逐个地mmap每条记录,除非你的“记录”非常大——那样会非常慢,每条记录需要2个系统调用,而且可能会从磁盘内存缓存.....中丢失页面
在你的情况下,我认为mmap(), istream和低级的open()/read()调用都是相同的。在这些情况下,我建议使用mmap():
文件中有随机访问(而不是顺序访问)和
或者在文件中存在引用位置,以便某些页面可以映射进来,其他页面可以映射出去。这样操作系统就能最大限度地利用可用RAM。
或者,如果多个进程正在读取/处理同一个文件,那么mmap()非常有用,因为所有进程都共享相同的物理页面。
(顺便说一下-我喜欢mmap()/MapViewOfFile())。
我认为mmap最大的优点是可以实现异步读取:
addr1 = NULL;
while( size_left > 0 ) {
r = min(MMAP_SIZE, size_left);
addr2 = mmap(NULL, r,
PROT_READ, MAP_FLAGS,
0, pos);
if (addr1 != NULL)
{
/* process mmap from prev cycle */
feed_data(ctx, addr1, MMAP_SIZE);
munmap(addr1, MMAP_SIZE);
}
addr1 = addr2;
size_left -= r;
pos += r;
}
feed_data(ctx, addr1, r);
munmap(addr1, r);
问题是我找不到正确的MAP_FLAGS来提示这个内存应该尽快从文件同步。
我希望MAP_POPULATE为mmap提供了正确的提示(即它不会尝试在调用返回之前加载所有内容,但会在异步中这样做。feed_data)。至少使用这个标志可以得到更好的结果,即使手册上说自2.6.23以来没有MAP_PRIVATE它什么都不做。