【问题标题】:How does mmap improve file reading speed?mmap 如何提高文件读取速度?
【发布时间】:2016-05-11 20:32:58
【问题描述】:

假设地址空间可以覆盖文件,在我看来,mmap 只是分配一块与即将读取的文件一样大的内存,并在它们对应的块之间创建一对一的关系。但是,为什么这样做会加快文件读取速度?看来,为了真正得到文件的内容,你仍然必须去磁盘,并读取它上面的所有字节。

与 malloc 相同大小的内存并手动将整个文件读入 malloc 区域相比,它有什么区别?

【问题讨论】:

  • 它确实加快阅读速度;必须从磁盘读取相同数量的块。从程序员的角度来看,它只会(有时)更方便。而且mmap不分配一块内存,它分配的是地址空间,真正的数据是faulted in以后,一旦被引用。

标签: linux posix mmap memory-mapped-files


【解决方案1】:

mmap 的工作方式不同。它是可预见的并适应程序的访问模式。此外,还可以通过madvise 设置具体政策,以进一步微调使用情况。

要更深入地讨论mmap 在按需分页环境中的工作原理,请在此处查看我的回答:Which segments are affected by a copy-on-write?,因为它还讨论了mmap 的使用

mmap 是通过execve 等执行程序的命脉。人。所以,你可以打赌它很快。附带说明一下,具有讽刺意味的是,malloc 实际上也使用匿名 mmap

但是,对于此处的讨论,请特别注意带有 mmap 的文件与使用 mallocread(2) 的文件的“后备存储”(即分页磁盘)

对于mmap,内存区域的后备存储是文件本身。该区域将页面直接映射到内核的文件系统缓冲区页面[它们已经统一了很长时间]。因此,不需要像 read(2) 那样从内核文件系统缓冲区页面到应用程序页面的 [浪费] 复制。

当您执行malloc/read 时,您仍然拥有上述页面,但是,现在 malloc 的区域在分页/交换磁盘上有一个后备存储。因此,页面缓冲区的数量是mmap两倍。正如我所提到的,读取完成后必须将数据复制到该区域中。

此外,就性能而言,进行大量读取不是最佳选择。建议的大小约为 64 KB,一个块 [取决于文件系统]。

当您进行大量读取时,您的程序在完成之前无法启动。如果文件的大小大于物理内存,系统会读入你的 malloc 区域,并且会浪费性地开始将较早的页面刷新到分页磁盘,以便为接近文件末尾的页面腾出空间,直到整个文件被阅读。

换句话说,当这个大的预读发生时,应用程序正在等待[并且什么都不做]。对于[比如说] 60 GB 的文件,启动时间会很明显

如果您的文件确实足够大,您甚至会用完分页磁盘上的空间(即malloc 返回 NULL)。

对于mmap,不存在此类问题。当您映射一个文件时,您可以立即开始使用它。它将直接从该区域的后备存储[再次是文件系统中的文件]按需“故障”。而且,如果您有 [比如说] 1 TB 的文件,mmap 就可以很好地处理。

此外,您可以通过madvise(2)posix_madvise(2) 逐页或任何页面范围(包括整个文件)控制映射策略。 madvise 系统调用相对轻量级,因此可以大量使用。这是一个提示,但不会执行会延迟应用程序的 I/O。如果 I/O 开始预读提示,它是由内核作为后台活动完成的。

您甚至可以告诉系统很快将需要给定页面 [系统将此作为预取它的提示],或者您可以告诉系统不再需要该页面 [并且系统将释放页缓冲内存]。

您可以对整个文件说“顺序访问”之类的话,这意味着系统将知道自动进行预读,以及释放不再需要的页面(即,如果您当前正在访问第 N 页,则系统释放 N-k 之前的任何页面)

当您执行read(2) 时,无法告诉系统不再需要给定的内核 FS 页面缓冲区。它们会一直徘徊,直到物理 RAM 填满 [或超出给定限制],这会增加整个内存系统的压力。

在实践中,使用read,我发现在应用程序移动到文件的不同部分或完全不同的文件后,用于 FS 缓冲区的内存量仍然很高。事实上,我已经看到一个 I/O 密集型应用程序使用了如此多的缓冲区,以至于导致不相关的 [空闲] 进程的页面被盗并刷新到页面磁盘。当我停止 I/O 应用程序时,firefox 需要几分钟时间才能将自身分页并再次响应。

我为常规读取与 mmap 做了一些广泛的基准测试。通过它们,mmap 可以提高某些应用程序的速度。

在这里查看我的答案:read line by line in the most efficient way *platform specific*

在我这样做之前,我对 mmap 的好处持怀疑态度,但基准测试表明 mmap 是赢家。

此外,如果您正在使用read(2)(速度)与fgets,如果给定行跨越读取缓冲区边界(即最后 50缓冲区的字符具有 80 字符行的前 50 个字节)。

请注意,在此链接页面的 cmets 中,还有另一个指向 pastebin 的链接,指向我的基准程序的更高版本,结果太大而无法在上述 SO 答案上发布,该答案对各种 madvise 选项进行基准测试和比较

【讨论】:

  • 那为什么 mmap 更快呢?似乎 mmap 需要一个接一个地到达块,而其他流式函数一次获取大量数据。在我看来, mmap 将花费更多时间来进行磁盘旋转和读取。
  • @OneZero 我添加了一个新链接,指向我之前的答案 [我已经忘记了]。我还在我的回答中添加了 很多 更多解释 mmap 的实际工作原理。它不像你认为的那样有效。它要复杂得多。此外,我还添加了与 malloc/read 方法的比较——它实际上不如人们想象的那么理想。
  • 该死的好答案。
  • @DavidC.Rankin 你好,大卫。谢谢。如果你喜欢这个答案,你应该会看到 stackoverflow.com/questions/39185134/… 它刚刚获得了“Nice Answer”徽章。 法律免责声明:鼓励连续投票
  • 我总是喜欢深入研究分页的内部工作原理以及尽可能避免使用用户空间来优化的方法。我在文件复制例程计时中短暂涉足mmap 区域。我了解了更多关于sendfile 内核空间副本(我理解它相当于mmap/memcpy),但没有深入了解mmap 本身。感谢您的链接和答案。
【解决方案2】:

我对此很好奇,所以我尝试对以下文件的整个文件读取进行基准测试 大小为 1、2、4、8 等,一次使用 mmap (M) 一次使用 read (R)(理论上是一次使用 fstat-ed 大小的调用,但如果该调用返回一个部分,它将重试结果)。在读取/映射之后,每个映射/读取页面的一个字节以不可优化的方式访问。

这是我的结果:

Size   M(µs)   R(µs)
1      9.5     4.2
2      10.8    4.5
4      8.4     3.8
8      8.6     3.8
16     7.3     4
32     7.8     3.5
64     8.3     3.9
128    9.2     4.6
256    8.6     4.7
512    10.6    5.1
1.0Ki  9.8     4.7
2.0Ki  10.1    5.4
4.0Ki  10.5    5.6
8.0Ki  10.4    6.9
16Ki   9.9     10
32Ki   14.4    12.8
64Ki   16.1    23.7
128Ki  28.1    41.1
256Ki  34.5    82.4
512Ki  57.9    154.6
1.0Mi  103.5   325.8
2.0Mi  188.5   919.8
4.0Mi  396.3   1963.2
8.0Mi  798.8   3885
16Mi   1611.4  7660.2
32Mi   3207.4  23040.2
64Mi   6712.1  84491.9

看来read 的速度大约是16Ki 的两倍。从那时起,mmap 开始大获全胜(64MiB 文件的倍数为 12)。

(在我的笔记本电脑上使用 3.19 在 Linux 上测试,10^4 次重复读取同一个文件。)

【讨论】:

    【解决方案3】:

    它没有。具体来说, mmap() 在您调用它时不会将整个文件加载到内存中,这是为了加快访问速度。相反,它映射文件,也就是说,在内存中创建文件的索引(我松散地使用该术语,请耐心等待),以便在您尝试读取/写入该“键”时触发页面错误”。所以最终的效果是你有一个简单的文件接口和文件内容的一种延迟加载。

    我可以继续,但其他人做得更好。例如,请参阅here

    【讨论】:

    • 似乎使用延迟加载,内存映射只会更慢?从现在开始,每次访问页面时都必须旋转磁盘并读取块,而不是一次读取大量页面。是吗?
    猜你喜欢
    • 2016-11-23
    • 1970-01-01
    • 2013-08-10
    • 2019-03-21
    • 2015-06-15
    • 1970-01-01
    • 2019-06-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多