加速文件I/O: mmap() vs. read()

Speeding up file I/O: mmap() vs. read()

本文关键字:vs read mmap 文件 加速      更新时间:2023-10-16

我有一个Linux应用程序,可以并行读取150-200个文件(4-10GB)。每个文件依次以小的、可变大小的块读取,通常每个小于2K。

我目前需要从一组文件中保持超过200mb/s的读取速率。磁盘可以很好地处理这些。预计需要超过1 GB/s(目前超出了磁盘的能力范围)。

我们实现了两个不同的读取系统,它们都大量使用posix_advise:首先是mmap ed读取,我们映射整个数据集并按需读取。第二种是基于read()/seek()的系统。

这两种方法都可以很好地工作,但只适用于中等情况,read()方法管理我们的整体文件缓存要好得多,可以很好地处理100 GB的文件,但速率有限,mmap能够预缓存数据,使持续的数据速率超过200MB/s易于维护,但不能处理大的总数据集大小。

所以我的问题是:

A: read()类型的文件i/o是否可以在Linux上进一步优化posix_advise调用,或者已经调优了磁盘调度器,VMM和posix_advise调用,这是我们所期望的那样好吗?

是否有系统的方法可以让mmap更好地处理非常大的映射数据?

Mmap-vs-reading-blocks是一个与我正在研究的问题类似的问题,并为这个问题提供了一个很好的起点,以及map-vs-read中的讨论。

read back to what?这些数据的最终目的地是什么?

因为听起来你完全是IO绑定,mmapread应该没有区别。有趣的部分是如何将数据传递到接收器。

假设您将这些数据放入管道中,我建议您将每个文件的全部内容转储到管道中。要使用零复制完成此操作,请尝试splice系统调用。您也可以尝试手动复制文件,或者创建一个cat的实例或其他一些可以大量缓冲当前文件作为标准输入、管道作为标准输出的工具。

if (pid = fork()) {
    waitpid(pid, ...);
} else {
    dup2(dest, 1);
    dup2(source, 0);
    execlp("cat", "cat");
}

Update0

如果您的处理与文件无关,并且不需要随机访问,则需要使用上面列出的选项创建管道。您的处理步骤应该接受来自stdin或管道的数据。

回答你更具体的问题:

A:除了Linux上的posix_advise调用之外,read()类型的文件i/o是否可以进一步优化,或者已经调优了磁盘调度器、VMM和posix_advise调用,这是否如我们所期望的那样好?

在告诉内核从用户空间做什么方面,这是最好的。剩下的取决于你:缓冲,线程等,但这是危险的,可能是无效的猜测工作。我只需要将文件拼接到一个管道中。

是否有系统的方法可以让mmap更好地处理非常大的映射数据?

是的。以下选项可能会给您带来出色的性能优势(并且可能使mmap值得在测试时使用over read):

  • MAP_HUGETLB使用"大页面"分配映射。

    这将减少内核中的分页开销,如果您将映射千兆字节大小的文件,这将非常有用。

  • MAP_NORESERVE不要为这个映射保留交换空间。当交换空间被保留时,可以保证可以修改映射。当交换空间不保留时,如果没有可用的物理内存,则在写操作时可能会遇到SIGSEGV。

    如果你没有足够的物理内存,这将防止你耗尽内存,同时保持你的实现简单。**

  • MAP_POPULATE为映射填充(预错误)页表。对于文件映射,这会导致文件的预读。以后对映射的访问不会因页面错误而受阻。

    如果有足够的硬件资源,如果预取是有序的,并且是惰性的,这可能会给你加速。我怀疑这个标志是多余的,VFS可能在默认情况下做得更好。

如果您的程序可以提前预测它想要读取的文件片段(但这只是猜测,我可能是错的),也许使用readahead系统调用可能会有所帮助。

我认为你应该调整你的应用程序,甚至是你的算法,以读取比几千字节大得多的数据块。难道不能只有半兆字节吗?

这里的问题似乎不是使用哪个api。无论使用mmap()还是read(),磁盘仍然必须查找到指定的点并读取数据(尽管操作系统确实有助于优化访问)。

mmap()在读取非常小的块(几个字节)时比read()有优势,因为您不需要为每个块调用操作系统,这会变得非常慢。

我还建议像Basile那样连续读取超过2kb,这样光盘就不必经常查找了