`mmap()` 手动并发预故障/分页

`mmap()` manual concurrent prefaulting / paging

我正在尝试微调 mmap() 以对可能非常大的文件执行快速写入或读取(通常不是两者)。写入和读取将 大部分 在一次传递中是连续的,然后在未来的传递中可能非常稀疏。不需要多次访问内存区域。

换句话说,将其视为具有一些异步修复的损失的文件传输。

正如预期的那样,mmap() 性能的主要限制似乎是它在大文件上生成的次要页面错误的数量。此外,我怀疑 Linux 内核的页面到磁盘的惰性导致了一些性能问题。即,任何最终对 mmaped 内存执行大量写入的测试程序在执行所有写入以终止/munmap 内存后似乎需要很长时间。

我希望通过在执行几乎顺序的访问和调出我不再需要的页面的同时对页面进行预故障来抵消这些故障的成本。但我对这种方法和我对问题的理解有三个主要问题:

  1. 是否有直接的(最好是 POSIX [或至少 OSX] 兼容的)执行部分预故障的方法?我知道 MAP_POPULATE 标志,但这似乎试图将整个文件加载到内存中,这在许多情况下是无法容忍的。此外,这似乎导致 mmap() 调用阻塞,直到预故障完成,这也是无法容忍的。我对手动替代方案的想法是生成一个线程,只是为了尝试读取内存中的下一个 N 页以强制预取。但实际上 madviseMADV_SEQUENTIAL 可能已经这样做了。
  2. msync() 可用于将更改刷新到磁盘。然而,定期这样做真的有用吗?我的想法是,如果程序经常处于磁盘 IO 的 "Idle" 状态并且能够承受一些磁盘写回,它可能会有用。话又说回来,内核很可能比以往的应用程序更好地处理它。
  3. 我对磁盘IO的理解准确吗?我的假设是预故障和 reading/writing 页面可以由不同的线程或进程 同时 完成;如果我错了,那么手动预故障根本就没有用。类似地,如果 msync() 调用阻塞所有磁盘 IO,包括文件系统缓存和原始文件系统,那么在程序终止时使用它而不是刷新整个磁盘缓存的动机也不大.

It appears, as expected, that the main limitation of mmap()'s performance seems to be the number of minor page faults it generates on large files.

这并不奇怪,我同意。但这是无法避免的成本,至少对于您实际访问的映射文件区域对应的页面而言。

Furthermore, I suspect the laziness of the Linux kernel's page-to-disk is causing some performance issues. Namely, any test programs that end up performing huge writes to mmaped memory seem to take a long time after performing all writes to terminate/munmap memory.

这是有道理的。同样,这是不可避免的成本,至少对于脏页而言是这样,但您可以对何时产生这些成本施加一些影响。

I was hoping to offset the cost of these faults by concurrently prefaulting pages while performing the almost-sequential access and paging out pages that I won't need again. But I have three main questions regarding this approach and my understanding of the problem:

  1. Is there a straightforward (preferably POSIX [or at least OSX] compatible) way of performing a partial prefault? I am aware of the MAP_POPULATE flag, but this seems to attempt loading the entire file into memory,

是的,这与其文档一致。

which is intolerable in many cases. Also, this seems to cause the mmap() call to block until prefaulting is complete,

这也是有记载的。

which is also intolerable. My idea for a manual alternative was to spawn a thread simply to try reading the next N pages in memory to force a prefetch.

除非在您最初 mmap() 文件和您想要开始访问映射之间存在延迟,否则我不清楚为什么您会期望它提供任何改进。

But it might be that madvise with MADV_SEQUENTIAL already does this, in effect.

如果您想要 POSIX 兼容性,那么您正在寻找 posix_madvise()。我确实建议使用此功能,而不是尝试推出您自己的用户空间替代方案。特别是,如果您使用 posix_madvise() 在某些或所有映射区域上断言 POSIX_MADV_SEQUENTIAL,那么希望内核能够提前读取以在需要页面之前加载页面是合理的。此外,如果您使用 POSIX_MADV_DONTNEED 建议,那么根据内核的判断,您可能会更早地同步到磁盘并减少总体内存使用量。如果有用,您也可以通过此机制传递其他建议。

  1. msync() can be used to flush changes to the disk. However, is it actually useful to do this periodically? My idea is that it might be useful if the program is frequently in an "Idle" state of disk IO and can afford to squeeze in some disk writebacks. Then again, the kernel might very well be handling this itself better than the ever application could.

这是要测试的东西。请注意,msync() 支持 异步 同步,因此您不需要 I/O 闲置。因此,当您确定已完成给定页面时,您可以考虑 msync() 使用标志 MS_ASYNC 来请求内核安排同步。这可能会减少取消映射文件时产生的延迟。您必须尝试将它与 posix_madvise(..., ..., POSIX_MADV_DONTNEED) 结合使用;它们可能会或可能不会相互补充。

  1. Is my understanding of disk IO accurate? My assumption is that prefaulting and reading/writing pages can be done concurrently by different threads or processes; if I am wrong about this, then manual prefaulting would not be useful at all.

一个线程应该有可能预置页面(通过访问它们),而另一个线程读取或写入其他已经出错的页面,但我不清楚为什么你期望这样一个预置线程能够运行 在执行读写操作之前。如果它有任何影响(即如果内核不自行预故障)那么我希望预故障页面比读取或写入每个字节一次更昂贵。

Similarly, if an msync() call blocks all disk IO, both to the filesystem cache and to the raw filesystem, then there also isn't as much of an incentive to use it over flushing the entire disk cache at the program's termination.

代表您的程序需要执行的磁盘读取和写入次数最少。对于任何给定的映射文件,它们都将在同一 I/O 设备上执行,因此它们都将相对于彼此进行序列化。如果您 I/O 绑定到第一个近似值,则执行这些 I/O 操作的顺序对总体 运行 时间无关紧要。

因此,如果 运行time 是您所关心的,那么 posix_madvise()msync() 可能都没有多大帮助,除非您的程序花费了很大一部分时间它的 运行 独立于访问映射文件的任务的时间。如果您确实发现自己没有完全 I/O 绑定,那么我的建议是先看看 posix_madvise() 能为您做什么,如果您需要更多,则尝试异步 msync()。我倾向于怀疑用户空间预故障或同步 msync() 是否会带来胜利,但在优化中,测试总是比(仅)预测更好。