我正在开发一个程序,可以处理100GB或更大的文件。文件包含可变长度的记录集。我已经有了第一个实现并运行,现在正在寻求提高性能,特别是在更有效地做I/O,因为输入文件被扫描了很多次。

是否有使用mmap()与通过c++的fstream库读取块的经验法则?我想做的是将大块从磁盘读入缓冲区,处理缓冲区中的完整记录,然后读取更多数据。

mmap()代码可能会变得非常混乱,因为mmap的块需要位于页面大小的边界上(我的理解),而记录可能位于页面边界上。使用fstreams,我可以只寻找记录的开始并重新开始读取,因为我们不局限于读取位于页面大小边界上的块。

如果不首先编写完整的实现,我如何在这两个选项之间做出决定呢?有什么经验法则(例如,mmap()快2倍)或简单的测试吗?


当前回答

I remember mapping a huge file containing a tree structure into memory years ago. I was amazed by the speed compared to normal de-serialization which involves lot of work in memory, like allocating tree nodes and setting pointers. So in fact I was comparing a single call to mmap (or its counterpart on Windows) against many (MANY) calls to operator new and constructor calls. For such kind of task, mmap is unbeatable compared to de-serialization. Of course one should look into boosts relocatable pointer for this.

其他回答

我试图找到关于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可用的大小)。

另外,您能否在处理下一条记录之前完成每条记录的所有处理步骤?也许这样可以避免一些IO开销?

主要的性能成本是磁盘i/o。"mmap()"当然比istream快,但这种差异可能不明显,因为磁盘I / O将主导您的运行时。

我尝试了Ben Collins的代码片段(见上面/下面)来测试他的断言“mmap()快得多”,并没有发现可测量的差异。请看我对他的回答的评论。

我当然不建议逐个逐个地mmap每条记录,除非你的“记录”非常大——那样会非常慢,每条记录需要2个系统调用,而且可能会从磁盘内存缓存.....中丢失页面

在你的情况下,我认为mmap(), istream和低级的open()/read()调用都是相同的。在这些情况下,我建议使用mmap():

文件中有随机访问(而不是顺序访问)和 或者在文件中存在引用位置,以便某些页面可以映射进来,其他页面可以映射出去。这样操作系统就能最大限度地利用可用RAM。 或者,如果多个进程正在读取/处理同一个文件,那么mmap()非常有用,因为所有进程都共享相同的物理页面。

(顺便说一下-我喜欢mmap()/MapViewOfFile())。

在我看来,使用mmap()“只是”使开发人员不必编写自己的缓存代码。在一个简单的“每读一次文件”的情况下,这并不难(尽管mlbrock指出,您仍然将内存副本保存到进程空间中),但如果您在文件中来回执行或跳过位等等,我相信内核开发人员在实现缓存方面可能比我做得更好……

我很抱歉本·柯林斯丢失了他的滑动窗口mmap源代码。这在Boost中是很好的。

是的,映射文件要快得多。您实际上是在使用OS虚拟内存子系统来关联内存和磁盘,反之亦然。可以这样想:如果OS内核开发者可以让它更快,他们会的。因为这样做几乎使所有事情都更快:数据库、启动时间、程序加载时间等等。

滑动窗口方法实际上并不难,因为可以一次映射多个连续的页面。因此,记录的大小并不重要,只要最大的记录可以放入内存。重要的是做好簿记工作。

如果一个记录不是从getpagesize()边界开始,那么映射就必须从前一页开始。映射区域的长度从记录的第一个字节(如有必要向下舍入到getpagesize()的最近倍数)扩展到记录的最后一个字节(四舍五入到getpagesize()的最近倍数)。当您完成一条记录的处理后,您可以unmap()它,然后继续到下一条记录。

这在Windows下工作也很好,使用CreateFileMapping()和MapViewOfFile()(和GetSystemInfo()来获取SYSTEM_INFO。dwAllocationGranularity——不是SYSTEM_INFO.dwPageSize)。