我正在尝试微调mmap()
以对可能非常大的文件执行快速写入或读取(通常不是全部)。一次读和写大部分将是连续的,然后在将来的读中可能非常稀疏。不需要多次访问任何内存区域。
换句话说,可以将其视为具有一些有损性的文件传输,并通过异步方式对其进行修复。
正如预期的那样,mmap()
性能的主要限制似乎是在大文件上生成的次要页面错误的数量。此外,我怀疑Linux内核的页面到磁盘的惰性导致了一些性能问题。即,任何最终对mmap
ed存储器执行大量写操作的测试程序在执行所有终止/munmap
存储器的写操作后似乎都需要很长时间。
我希望通过同时对页面进行预故障处理,同时执行几乎不需要的访问和分页来抵消这些故障的成本。但是我对这种方法和对该问题的理解有三个主要问题:
MAP_POPULATE
标志,但这似乎是试图将整个文件加载到内存中,这在许多情况下是无法忍受的。同样,这似乎导致mmap()
调用阻塞,直到完成预故障为止,这也是无法忍受的。我的手动替代方法的想法是产生一个线程,只是尝试读取内存中的下一个N
页以强制进行预取。但是,实际上madvise
和MADV_SEQUENTIAL
可能已经做到了这一点。 msync()
可用于刷新对磁盘的更改。但是,定期执行此操作实际上有用吗?我的想法是,如果程序经常处于磁盘IO的“空闲”状态并且可以承受某些磁盘写回操作,则可能会很有用。再说一次,内核可能会比以往的应用程序更好地处理此问题。 msync()
调用阻止了所有磁盘IO(包括文件系统缓存和原始文件系统),那么在程序终止时刷新整个磁盘缓存的动机就没有太大的动机了。 最佳答案
我同意,这并不奇怪。但这是无法避免的成本,至少对于与您实际访问的映射文件区域相对应的页面而言。
这是合理的。同样,这是不可避免的成本,至少对于脏页而言,但是您可以对何时产生这些成本产生一定的影响。
是的,这与其文档一致。
也有记载。
除非在最初mmap()
文件与开始访问映射之间没有延迟,否则我不清楚您为什么期望可以提供任何改进。
如果您想要POSIX兼容性,那么您正在寻找posix_madvise()
。我确实建议使用此功能,而不要尝试使用自己的用户空间替代方法。特别是,如果您使用posix_madvise()
在某些或所有映射区域上声明POSIX_MADV_SEQUENTIAL
,则可以合理地希望内核会在需要页面之前先读取它们以加载页面。另外,如果您建议使用POSIX_MADV_DONTNEED
,那么您可以根据内核的判断,更早地同步到磁盘,总体上减少内存使用。如果有用的话,您还可以通过此机制通过其他建议。
这是要测试的东西。请注意,msync()
支持异步同步,因此您不需要I/O空闲。因此,当您确定已完成给定页面时,可以考虑使用msync()
标志MS_ASYNC
对其进行编码,以请求内核安排同步。这可以减少您取消映射文件时产生的延迟。您将不得不尝试将其与posix_madvise(..., ..., POSIX_MADV_DONTNEED)
结合使用;他们可能相辅相成。
一个线程应该有可能对页面进行预故障处理(通过访问它们),而另一个线程则可以读取或写入其他已经出错的页面,但是我不清楚为什么您期望这样的预故障处理线程能够在页面之前运行(s)进行读取和写入。如果它根本没有任何作用(即,如果内核本身不进行预故障处理),那么我希望对页进行预故障处理要比一次读取或写入其中的每个字节都昂贵。
必须以您的程序名义执行的最小磁盘读取和写入次数。对于任何给定的映射文件,它们都将在同一个I/O设备上执行,因此它们将彼此相对序列化。如果您受I/O约束,则可以近似为一阶近似值,这些I/O操作的执行顺序对于整个运行时都无关紧要。
因此,如果您关心的是运行时,那么除非您的程序将其运行时的大部分时间都花在与访问映射文件无关的任务上,否则posix_madvise()
和msync()
都不会有太大帮助。如果您发现自己不完全受I/O约束,那么我的建议是先看看posix_madvise()
能为您做什么,如果需要更多的功能,请尝试异步msync()
。我倾向于怀疑用户空间预故障或同步msync()
是否会成功,但是在优化方面,测试总比(仅)预测好。
关于c - `mmap()`手动并发预故障/分页,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/47677137/