为啥 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() 快的主要内容,如果未能解决你的问题,请参考以下文章

mmap

Linux之共享内存shm和内存映射mmap

mmap

iOS之深入解析文件内存映射MMAP

mmap函数实现

mmap函数