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

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

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

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


当前回答

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

其他回答

我试图找到关于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就没有什么意义)。如果按顺序访问数据,则正常读取文件或在读取后丢弃文件。如果任何一种方法能让你的程序不那么复杂,那就这么做。对于许多真实世界的案例,如果不测试实际应用程序,而不是基准测试,就没有确定的方法来显示一个更快。

(对不起,我想问这个问题,但我一直在寻找答案,这个问题一直出现在谷歌结果的顶部。)

这听起来像是多线程的一个很好的用例……我认为你可以很容易地设置一个线程读取数据,而其他(s)处理它。这可能是一种显著提高感知表现的方法。只是一个想法。

Mmap要快得多。你可以写一个简单的基准测试来证明:

char data[0x1000];
std::ifstream in("file.bin");

while (in)
{
  in.read(data, 0x1000);
  // do something with data
}

对比:

const int file_size=something;
const int page_size=0x1000;
int off=0;
void *data;

int fd = open("filename.bin", O_RDONLY);

while (off < file_size)
{
  data = mmap(NULL, page_size, PROT_READ, 0, fd, off);
  // do stuff with data
  munmap(data, page_size);
  off += page_size;
}

显然,我省略了一些细节(例如,如果文件不是page_size的倍数,如何确定何时到达文件的末尾),但实际上不应该比这复杂得多。

如果可以,可以尝试将数据分解为多个文件,这些文件可以整体而不是部分地使用mmap()进行编辑(简单得多)。

几个月前,我为boost_iostreams实现了一个不成熟的滑动窗口mmap()-ed流类,但没有人关心,我忙着做其他事情。最不幸的是,几周前我删除了一个旧的未完成项目的存档,这是受害者之一:-(

更新:我还应该补充一个警告,这个基准测试在Windows中看起来会有很大不同,因为微软实现了一个漂亮的文件缓存,它首先完成了您在mmap中所做的大部分工作。例如,对于经常访问的文件,你可以执行std::ifstream.read(),它会和mmap一样快,因为文件缓存已经为你做了一个内存映射,而且它是透明的。

Final Update: Look, people: across a lot of different platform combinations of OS and standard libraries and disks and memory hierarchies, I can't say for certain that the system call mmap, viewed as a black box, will always always always be substantially faster than read. That wasn't exactly my intent, even if my words could be construed that way. Ultimately, my point was that memory-mapped i/o is generally faster than byte-based i/o; this is still true. If you find experimentally that there's no difference between the two, then the only explanation that seems reasonable to me is that your platform implements memory-mapping under the covers in a way that is advantageous to the performance of calls to read. The only way to be absolutely certain that you're using memory-mapped i/o in a portable way is to use mmap. If you don't care about portability and you can rely on the particular characteristics of your target platforms, then using read may be suitable without sacrificing measurably any performance.

编辑以清除答案列表: @jbl:

滑动窗口mmap发出声音 有趣。你能多说一点吗 呢?

当然-我正在为Git写一个c++库(一个libgit++,如果你愿意的话),我遇到了一个类似的问题:我需要能够打开大(非常大)的文件,而不是有一个完全的性能狗(因为它将与std::fstream)。

Boost::Iostreams already has a mapped_file Source, but the problem was that it was mmapping whole files, which limits you to 2^(wordsize). On 32-bit machines, 4GB isn't big enough. It's not unreasonable to expect to have .pack files in Git that become much larger than that, so I needed to read the file in chunks without resorting to regular file i/o. Under the covers of Boost::Iostreams, I implemented a Source, which is more or less another view of the interaction between std::streambuf and std::istream. You could also try a similar approach by just inheriting std::filebuf into a mapped_filebuf and similarly, inheriting std::fstream into a mapped_fstream. It's the interaction between the two that's difficult to get right. Boost::Iostreams has some of the work done for you, and it also provides hooks for filters and chains, so I thought it would be more useful to implement it that way.

也许您应该对文件进行预处理,这样每个记录都在一个单独的文件中(或者至少每个文件都是mmap可用的大小)。

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

我同意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仍然是赢家,但这应该使事情更接近。