`mmap()`手动并发prefaulting / paging

时间:2017-12-06 14:51:03

标签: c linux concurrency mmap page-fault

我试图微调mmap()以执行可能非常大的文件的快速写入或读取(通常不是两者)。写入和读取将在一次通过时主要顺序,然后在将来的通过时可能非常稀疏。不需要多次访问任何内存区域。

换句话说,将其视为具有一些不连续修复的文件传输。

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

我希望通过同时预先显示的页面来抵消这些错误的成本,同时执行几乎顺序的访问和分页,我不再需要这些页面。但是我对这种方法以及对问题的理解有三个主要问题:

  1. 是否有直接(最好是POSIX [或至少兼容OSX])执行部分prefault的方式?我知道MAP_POPULATE标志,但这似乎尝试将整个文件加载到内存中,这在许多情况下是无法容忍的。此外,这似乎导致mmap()调用阻塞,直到预先完成,这也是无法容忍的。我想要手动替代的方法是生成一个线程,只是为了尝试读取内存中的下一个N页面来强制预取。但实际上madvise MADV_SEQUENTIAL可能已经这样做了。
  2. msync()可用于刷新磁盘更改。但是,定期执行此操作实际上是否有用?我的想法是,如果该程序经常处于“空闲”状态,那么它可能会很有用。磁盘IO的状态,可以挤压一些磁盘写回。然后,内核可能会比以前的应用程序更好地处理它本身。
  3. 我对磁盘IO的理解是否准确?我的假设是,可以通过不同的线程或进程同时同时完成prefaulting和读/写页面;如果我对此错了,那么手动预先判断根本就没用。类似地,如果msync()调用阻止所有磁盘IO,无论是文件系统缓存还是原始文件系统,那么也没有动力使用它来刷新整个磁盘缓存。节目的终止。

1 个答案:

答案 0 :(得分:2)

  

正如预期的那样,mmap()性能的主要限制似乎是它在大文件上生成的次要页面错误的数量。

我不同意,这并不特别令人惊讶。但这是一个无法避免的成本,至少对于您实际访问的映射文件区域对应的页面。

  

此外,我怀疑Linux内核的页面到磁盘的懒惰导致了一些性能问题。也就是说,任何最终对mmaped内存执行大量写入的测试程序在执行所有写入终止/ munmap内存后都需要很长时间。

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

  

我希望同时抵消这些故障的成本   执行几乎顺序访问时的预先显示页面   分页我不再需要的页面。但我有三个主要的   关于这种方法的问题以及我对问题的理解:

     
      
  1. 是否有直接(最好是POSIX [或至少兼容OSX])执行部分prefault的方式?我知道了   MAP_POPULATE标志,但这似乎尝试加载整个文件   进入记忆,
  2.   

是的,这与其文档一致。

  

在许多情况下是无法容忍的。而且,这似乎   导致mmap()调用阻止,直到预先完成,

这也是记录在案的。

  

其中   也是无法忍受的。我对手动替代方案的想法是产生一个   线程只是尝试读取内存中的下N个页面来强制a   预取。

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

  

但可能已经与MADV_SEQUENTIAL进行了混血   这样做,实际上。

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

  
      
  1. msync()可用于刷新磁盘更改。但是,定期执行此操作实际上是否有用?我的想法是它可能   如果程序经常处于“空闲”状态,则非常有用。磁盘IO的状态   并且能够挤进一些磁盘写回。然后,再次   内核可能比以往更好地处理这个本身   申请可以。
  2.   

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

  
      
  1. 我对磁盘IO的理解是否准确?我的假设是预先发现和读/写页面可以同时完成   不同的线程或过程;如果我错了,那就手动了   prefaulting根本没用。
  2.   

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

  

同样,如果是msync()   call会阻止所有磁盘IO,包括文件系统缓存和raw   文件系统,然后也没有使用它的动机   在程序的终止时刷新整个磁盘缓存。

需要代表您的程序执行最少数量的磁盘读写操作。对于任何给定的mmapped文件,它们都将在同一个I / O设备上执行,因此它们将相互序列化。如果您接受I / O约束,那么执行这些I / O操作的顺序对于整个运行时无关紧要。

因此,如果运行时是您所关注的,那么除非您的程序将大部分运行时间花费在任务上,否则posix_madvise()msync()可能都不会有太大帮助。独立于访问mmapped文件。如果您发现自己并非完全受I / O限制,那么我的建议是先查看posix_madvise()可以为您做什么,并在需要更多时尝试异步msync()。我倾向于怀疑用户空间prefaulting或同步msync()会提供胜利,但在优化中,测试总是比(仅)预测更好。