内存映射文件的优点是什么?


89

我一直在研究一个项目的内存映射文件,并且会很感激以前使用过或决定不使用它们的人的任何想法,为什么?

我尤其按重要性顺序关注以下方面:

  • 并发
  • 随机访问
  • 性能
  • 便于使用
  • 可移植性

Answers:


56

我认为这样做的好处是,与传统的读取文件方法相比,您可以减少所需的数据复制量。

如果您的应用程序可以在内存映射文件中“就地”使用数据,则可以不进行复制而直接输入数据。如果您使用系统调用(例如Linux的pread()),则通常涉及内核将数据从其自身的缓冲区复制到用户空间中。这种额外的复制不仅花费时间,而且通过访问该额外的数据副本会降低CPU缓存的效率。

如果实际上必须从磁盘读取数据(如在物理I / O中一样),则OS仍必须将其读取,因此页面错误的性能可能不比系统调用更好,但如果它们不需要(即已经在操作系统缓存中),从理论上讲性能应该要好得多。

不利的一面是,内存映射文件没有异步接口-如果您尝试访问未映射的页面,则会产生页面错误,然后使线程等待I / O。


内存映射文件的明显缺点是在32位OS上-您很容易用完地址空间。


4
至少在Windows上,您可以映射更大的mmap文件的多个32位视图-比尝试使用常规CRT功能处理非常大的文件更有效
Martin Beckett

@MarkR您写道“他的额外复制不仅花费时间,而且通过访问此额外的数据副本降低了CPU缓存的效率。 ” (重点是我的)。您能否解释一下内核中多余的缓冲区副本如何阻碍CPU缓存的有效性?
极客

4
@Geek访问两倍的内存=浪费两倍的缓存(非常近似)。
user253751

49

我在用户键入内容时使用了内存映射文件来实现“自动完成”功能。我在一个索引文件中存储了超过一百万个产品零件号。该文件具有一些典型的标头信息,但文件的大部分是按键字段排序的固定大小记录的巨大数组。

在运行时,文件将进行内存映射,并转换为C-stylestruct数组,然后进行二进制搜索以查找与用户类型匹配的部件号。实际上,实际上仅从磁盘读取文件的几个内存页面,无论在二进制搜索过程中命中了哪个页面。

  • 并发性-我遇到一个实现问题,有时它会在同一进程空间中多次将文件映射到内存中。我记得这是一个问题,因为有时系统找不到足够大的可用虚拟内存块来将文件映射到。解决方案是只映射一次文件,然后对所有调用进行处理。回想起来,使用完整的Windows服务会很酷。
  • 随机访问-二进制搜索当然是随机访问,并且闪电般快
  • 性能-查找速度非常快。当用户键入时,弹出窗口将显示匹配的产品部件号的列表,该列表会随着他们继续键入而缩小。键入时没有明显的延迟。

1
二进制搜索会不会很慢,因为每次尝试都会读取页面?还是操作系统足够聪明,可以有效地解决这一问题?
jjxtra

1
我想使用内存映射的I / O对于二进制搜索来说是一种浪费,因为该搜索将仅访问相对较远的内存位置中的几个单个键,但是对于每个此类请求,操作系统将在4k页中加载。但是话又说回来,文件部分没有太大变化,因此缓存有助于掩盖这一点。但严格来说,我相信在这里传统的寻求/阅读会更好。最终,这几天100万已经不多了。为什么不将其全部保留在RAM中呢?
2013年

5
@the猪和PsychoDad,我最初的回答是从2008年开始的,此内存映射自动完成功能的实际实现在2004-2005年左右。对于我们的用户群而言,消耗800-1000MB的物理内存来加载整个文件不是一个好的解决方案。内存映射解决方案非常快速有效。它踢了屁股,我回想起初中的早期。:)
Brian Ensink

@BrianEnsink:好的,这很有道理。我没想到每个条目都会多达1kB。那么分页方法当然会变得更有效率。不错:)

22

内存映射文件可用于替换读/写访问或支持并发共享。当将它们用于一种机制时,也会获得另一种机制。

与其在文件中查找,写入和读取,不如将其映射到内存中,并且仅访问期望它们所在的位。

这可能非常方便,并且取决于虚拟内存接口可以提高性能。之所以会出现性能提高,是因为操作系统现在可以管理以前的“文件I / O”以及所有其他编程内存访问,并且(理论上)可以利用分页算法等已经用于支持的算法。其余程序的虚拟内存。但是,它确实取决于基础虚拟内存系统的质量。我听说过轶事,说Solaris和* BSD虚拟内存系统可能比Linux的VM系统表现出更好的性能改进-但是我没有经验数据来支持这一点。YMMV。

当您考虑到多个进程通过映射的内存使用同一个“文件”的可能性时,并发就会出现。在读/写模型中,如果两个进程写入文件的同一区域,则可以完全确定一个进程的数据将到达文件中,从而覆盖另一个进程的数据。您会得到一个或另一个-但不会有些奇怪的混合。我必须承认,我不确定这是否是任何标准规定的行为,但是您几乎可以依靠它。(这实际上是一个很好的后续问题!)

相反,在映射的世界中,想象两个过程都是“书写”的。他们这样做是通过“存储”来完成的,这最终导致操作系统将数据分页到磁盘上。但是与此同时,可能会发生重叠的写入。

这是一个例子。假设我有两个进程都在偏移量1024处写入8个字节。进程1正在写入“ 11111111”,进程2正在写入“ 22222222”。如果他们使用文件I / O,那么可以想像,在操作系统的深处,有一个充满1s的缓冲区和充满2s的缓冲区,两者都指向磁盘上的同一位置。其中一个将首先到达那里,而另一个则将到达那里。在这种情况下,第二个获胜。 但是,如果我使用内存映射文件方法,则进程1将去往4个字节的内存存储,然后是另一个4字节的内存存储(假设不是最大内存存储大小)。流程2将做同样的事情。根据进程的运行时间,您可以期望看到以下任何一项:

11111111
22222222
11112222
22221111

解决方案是使用显式互斥-无论如何这可能是一个好主意。无论如何,您还是依靠O / S在读/写文件I / O情况下做“正确的事情”。

分类互斥原语是互斥体。对于内存映射文件,建议您查看一个内存映射的互斥锁,该互斥锁可通过(例如)pthread_mutex_init()使用。

使用一个陷阱进行编辑:使用映射文件时,通常会尝试在文件本身中嵌入指向文件中数据的指针(请考虑将链接列表存储在映射文件中)。您不想这样做,因为文件可能在不同的时间或不同的进程中映射到不同的绝对地址。而是在映射文件中使用偏移量。


1

并发将是一个问题。随机访问更容易性能非常好。便于使用。不太好 便携性-不太热。

很久以前,我已经在Sun系统上使用了它们,这就是我的想法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.