与malloc分配相同大小的内存并手动将整个文件读入malloc的区域相比,有何不同呢?
mmap
的工作方式略有不同,它是预测性的,并适应程序的访问模式。此外,可以通过madvise
设置特定策略以进一步微调使用。
关于在需求分页环境中mmap
的工作原理的更详尽讨论,请参见我在这里的回答:哪些段会受到写时复制的影响?,它还谈到了mmap
的使用。
mmap
是通过execve
等程序执行的命脉。所以,你可以肯定它很快。顺便说一句,有趣的是,malloc
实际上也使用匿名的mmap
。
但是,在这里讨论,特别要注意mmap
和使用malloc
和read(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 以进行提示读取,则由内核作为后台活动完成。read(2)
时,无法告诉系统给定的内核 FS 页面缓冲区不再需要。它们将持续存在,直到物理 RAM 填满(或超过给定限制),这会给整个内存系统带来压力。read
时,我看到用于 FS 缓冲区的内存量在应用程序转移到文件的不同部分或完全不同的文件之后仍然保持高水平。事实上,我曾经看到单个 I/O 密集型应用程序使用了如此多的缓冲区,以至于导致无关的(空闲)进程被其页面窃取并刷新到分页磁盘。当我停止 I/O 应用程序时,花费了几分钟的时间才能使 Firefox 分页并重新变得响应。read(2)
(为了加快速度)与fgets
相比,如果给定行跨越读取缓冲区边界(即,您的缓冲区的最后50个字符具有80个字符行的前50个字节),则可能会陷入缓冲区移位问题。mmap
如何工作的解释。它的工作方式并不像你想象的那样简单。它要复杂得多。此外,我还添加了与malloc/read
方法的比较--实际上它并不像人们认为的那样优秀。 - Craig Esteymmap
领域。我了解了更多关于sendfile
内核空间复制的知识(我理解它相当于mmap/memcpy
),但没有深入研究mmap
本身。感谢提供链接和答案。 - David C. Rankinmmap
来提高性能,请务必查看我的答案:https://dev59.com/MJDea4cB1Zd3GeqPiesP#33620968 它包含完整的代码和基准测试[具有令人惊讶的结果]。此外,请确保向下滚动评论以找到最终代码版本的pastebin链接。 - Craig Esteymmap
(M),一次使用read
(R)(理论上只需使用fstat大小调用一次,但如果该调用返回部分结果,它将重试)。在读取/映射后,以不可优化的方式访问了每个映射/读取页面的一个字节。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
看起来在大约 16Ki
之前,read
要快两倍左右。从那时起,mmap
就开始大幅领先了(对于 64MiB
文件,优势高达 12 倍)。
(在我的笔记本电脑上使用 Linux 3.19 进行测试,对同一文件进行了 $10^4$ 次重复读取。)
并不是这样的。具体来说,当您调用mmap()时,它不会将整个文件加载到内存中,以某种方式加快访问速度。相反,它映射文件,也就是说,在内存中创建了一个文件索引(我使用术语不严谨,请容忍),以便在尝试读取/写入该“键”时触发页面错误。因此,净效果是您拥有文件的简单接口和一种类似于文件内容的惰性加载。
我可以继续说下去,但其他人做得更好。例如,请参见此处。