为啥 mmap()(内存映射文件)比 read() 快
Posted
技术标签:
【中文标题】为啥 mmap()(内存映射文件)比 read() 快【英文标题】:Why mmap() (Memory Mapped File) is faster than read()为什么 mmap()(内存映射文件)比 read() 快 【发布时间】:2015-05-20 17:52:29 【问题描述】:我最近正在研究 Java NIO 的 MappedByteBuffer。 我读过一些关于它的帖子,他们都提到“mmap() 比 read() 快”
在我的结论中:
我对待 MappedByteBuffer == Memory Mapped File == mmap()
read() 必须通过:磁盘文件->内核->应用程序读取数据,因此它具有上下文切换和缓冲区复制
他们都说 mmap() 的复制或系统调用比 read() 少,但据我所知,它还需要在您第一次访问文件数据时从磁盘文件中读取。所以第一次读取:虚拟地址->内存->页面错误->磁盘文件->内核->内存。除了可以随机访问之外,最后 3 步(磁盘文件 -> 内核 -> 内存)与 read() 完全相同,那么 mmap() 如何比 read() 更少复制或系统调用?
mmap() 和swap 文件有什么关系,操作系统是否会将内存中使用最少的文件数据放入swap(LRU)?因此,当您第二次访问这些数据时,操作系统会从交换而不是磁盘文件中检索它们(无需复制到内核缓冲区),这就是 mmap() 复制和系统调用较少的原因?
在 java 中,MappedByteBuffer 是在堆外分配的(它是一个直接缓冲区)。那么当您从 MappedByteBuffer 读取时,是否意味着它需要从 java 堆外部向 java 堆多复制一份内存?
有人可以回答我的问题吗?谢谢:)
【问题讨论】:
对于第 4 部分,OS 磁盘缓存似乎也考虑在内。 除了使用模式的最终性能优势之外,内存映射文件比堆缓冲区更适合共享机器。内存映射文件可以在操作系统需要回收物理内存时保存和取消映射,并在程序再次访问时透明地重新映射。 【参考方案1】:1:是的,本质上就是 MappedByteBuffer。
2:“磁盘文件->内核”不一定涉及复制。
3:对于内存映射文件,一旦内核将文件读入其缓存,它可以简单地将缓存的那部分映射到您的进程中 - 而不必将数据从缓存复制到您的位置进程指定。
4:如果内核决定从内存映射文件中换出一个页面,它不会将该页面写入页面文件;它会在丢弃页面之前将页面写入 original 文件(它所映射的文件)。将其写入页面文件将是不必要的,并且会浪费页面文件空间。
5:是的。例如,如果您调用get(byte[])
,那么数据将从堆外映射复制到您的数组中。请注意,get(byte[])
等函数需要为 任何 类型的缓冲区复制数据 - 这并不特定于内存映射文件。
【讨论】:
【参考方案2】:你在比较苹果和橘子。 mmap()
比 read()
快,因为它不做任何 I/O。当您访问由映射产生的内存地址时,I/O 将被推迟。 那个 I/O 与read(),
大致相同,而那个 I/O 是否比read()
更快是一个非常有争议的问题。在我接受之前,我希望看到一个合适的基准。
我对待
MappedByteBuffer
== 内存映射文件==mmap()
好的。
read()
必须通过:磁盘文件->内核->应用程序读取数据,所以它有两次上下文切换和缓冲区复制
对比什么?
他们都说 mmap() 的复制或系统调用比
read(),
少
它的系统调用更少。它是否具有较少的复制取决于实现。通过 DMA 直接读取和写入数据当然是可能的,但特定操作系统是否这样做是特定于操作系统的。
但据我所知,它还需要在您第一次访问文件数据时从磁盘文件中读取。
正确。
所以它第一次读取:虚拟地址 -> 内存 -> 页面错误 -> 磁盘文件 -> 内核 -> 内存。除了可以随机访问之外,最后 3 步(磁盘文件 -> 内核 -> 内存)与 read() 完全相同,那么 mmap() 如何比 read() 更少复制或系统调用?
由于 DMA,如果实施的话。
mmap() 和交换文件有什么关系
分配给映射的内存是进程地址空间的一部分,它是虚拟的,可以进行交换,交换文件中必须有空间供它使用,就像任何其他内存一样。
操作系统是否会将内存中使用最少的文件数据放入交换(LRU)中?
没有。
所以当您第二次访问这些数据时,操作系统会从交换而不是磁盘文件中检索它们(无需复制到内核缓冲区),这就是为什么 mmap() 复制和系统调用较少的原因?
没有。这样做是非常错误的。
在 java 中,
MappedByteBuffer
是在堆外分配的(它是一个直接缓冲区)。
这没有意义。直接缓冲区不是在堆外分配的,而是由mmap()
或任何平台API 分配的,作为新 内存。不在堆中。 MappedByteBuffer
是直接缓冲区是正确的。
那么当你从 MappedByteBuffer 中读取数据时,是否意味着它需要从 java 堆外向 java 堆多复制一份内存?
是的,但不是出于上述原因。原因是您必须调用MappedByteBuffer.get()/put(),
,这本身就是一个额外的步骤。
【讨论】:
我不会称之为“比较苹果和橘子”。这是两种共享文件读取和写入目的的机制。听起来和我很相似。 我不是从原始帖子中得到的。事实上,OP 似乎在质疑这一说法。我认为他对使用mmap
读取可以通过read
访问的相同数据的全局性能比mmap
本身与read
本身的性能更感兴趣。
@zneak 我请你参考问题的标题。
我请您参考问题的正文。阅读任何内容时不要停留在标题处是标准做法。以上是关于为啥 mmap()(内存映射文件)比 read() 快的主要内容,如果未能解决你的问题,请参考以下文章