我正在开发一个程序,可以处理100GB或更大的文件。文件包含可变长度的记录集。我已经有了第一个实现并运行,现在正在寻求提高性能,特别是在更有效地做I/O,因为输入文件被扫描了很多次。
是否有使用mmap()与通过c++的fstream库读取块的经验法则?我想做的是将大块从磁盘读入缓冲区,处理缓冲区中的完整记录,然后读取更多数据。
mmap()代码可能会变得非常混乱,因为mmap的块需要位于页面大小的边界上(我的理解),而记录可能位于页面边界上。使用fstreams,我可以只寻找记录的开始并重新开始读取,因为我们不局限于读取位于页面大小边界上的块。
如果不首先编写完整的实现,我如何在这两个选项之间做出决定呢?有什么经验法则(例如,mmap()快2倍)或简单的测试吗?
我认为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它什么都不做。
我很抱歉本·柯林斯丢失了他的滑动窗口mmap源代码。这在Boost中是很好的。
是的,映射文件要快得多。您实际上是在使用OS虚拟内存子系统来关联内存和磁盘,反之亦然。可以这样想:如果OS内核开发者可以让它更快,他们会的。因为这样做几乎使所有事情都更快:数据库、启动时间、程序加载时间等等。
滑动窗口方法实际上并不难,因为可以一次映射多个连续的页面。因此,记录的大小并不重要,只要最大的记录可以放入内存。重要的是做好簿记工作。
如果一个记录不是从getpagesize()边界开始,那么映射就必须从前一页开始。映射区域的长度从记录的第一个字节(如有必要向下舍入到getpagesize()的最近倍数)扩展到记录的最后一个字节(四舍五入到getpagesize()的最近倍数)。当您完成一条记录的处理后,您可以unmap()它,然后继续到下一条记录。
这在Windows下工作也很好,使用CreateFileMapping()和MapViewOfFile()(和GetSystemInfo()来获取SYSTEM_INFO。dwAllocationGranularity——不是SYSTEM_INFO.dwPageSize)。
我试图找到关于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就没有什么意义)。如果按顺序访问数据,则正常读取文件或在读取后丢弃文件。如果任何一种方法能让你的程序不那么复杂,那就这么做。对于许多真实世界的案例,如果不测试实际应用程序,而不是基准测试,就没有确定的方法来显示一个更快。
(对不起,我想问这个问题,但我一直在寻找答案,这个问题一直出现在谷歌结果的顶部。)