我一直在为一个项目研究内存映射文件,如果以前使用过它们或决定不使用它们的人的任何想法,我将不胜感激,为什么?
我特别关注以下问题,按重要性排序:
- 并发
- 随机访问
- 表现
- 便于使用
- 可移植性
我一直在为一个项目研究内存映射文件,如果以前使用过它们或决定不使用它们的人的任何想法,我将不胜感激,为什么?
我特别关注以下问题,按重要性排序:
我认为优势在于您减少了与传统读取文件方法相比所需的数据复制量。
如果您的应用程序可以“就地”使用内存映射文件中的数据,那么它可以在不被复制的情况下进入;如果您使用系统调用(例如 Linux 的 pread() ),那么这通常涉及内核将数据从其自己的缓冲区复制到用户空间。这种额外的复制不仅需要时间,而且会通过访问这些额外的数据副本而降低 CPU 缓存的效率。
如果实际上必须从磁盘读取数据(如在物理 I/O 中),那么操作系统仍然必须读取它们,页面错误可能不会比系统调用更好的性能,但如果它们不要(即已经在操作系统缓存中),理论上性能应该要好得多。
不利的一面是,内存映射文件没有异步接口 - 如果您尝试访问未映射的页面,它会生成页面错误,然后使线程等待 I/O。
内存映射文件的明显缺点是在 32 位操作系统上 - 您很容易用完地址空间。
我在用户键入时使用了内存映射文件来实现“自动完成”功能。我在单个索引文件中存储了超过 100 万个产品部件号。该文件具有一些典型的标题信息,但文件的大部分是按关键字段排序的固定大小记录的巨大数组。
在运行时,文件被内存映射,转换为C
-stylestruct
数组,我们进行二进制搜索以在用户键入时找到匹配的部件号。实际上从磁盘中只读取了文件的几个内存页面——无论是在二进制搜索期间命中的页面。
内存映射文件可用于替换读/写访问,或支持并发共享。当您将它们用于一种机制时,您也会得到另一种机制。
与其在文件中查找、写入和读取,不如将其映射到内存中并简单地访问您期望它们所在的位。
这可以非常方便,并且根据虚拟内存接口可以提高性能。性能改进可能会发生,因为操作系统现在可以管理这个以前的“文件 I/O”以及所有其他程序内存访问,并且可以(理论上)利用它已经用来支持的分页算法等程序其余部分的虚拟内存。但是,它确实取决于底层虚拟内存系统的质量。我听说 Solaris 和 *BSD 虚拟内存系统可能比 Linux 的 VM 系统表现出更好的性能改进——但我没有经验数据来支持这一点。YMMV。
当您考虑多个进程通过映射内存使用相同“文件”的可能性时,并发性就出现了。在读/写模型中,如果两个进程写入文件的同一区域,您可以确信其中一个进程的数据会到达文件中,并覆盖另一个进程的数据。你会得到一个,或者另一个——但不是一些奇怪的混合。我不得不承认我不确定这是否是任何标准规定的行为,但这是你几乎可以依赖的东西。(这实际上是一个很好的后续问题!)
相反,在映射的世界中,想象两个过程都“写作”。他们通过执行“内存存储”来做到这一点,这导致 O/S 最终将数据分页到磁盘。但与此同时,可能会发生重叠写入。
这是一个例子。假设我有两个进程都在偏移量 1024 处写入 8 个字节。进程 1 正在写入“11111111”,进程 2 正在写入“22222222”。如果他们使用文件 I/O,那么你可以想象,在 O/S 的深处,有一个充满 1s 的缓冲区和一个充满 2s 的缓冲区,它们都指向磁盘上的同一个地方。其中一个将首先到达那里,另一个将在第二个到达那里。在这种情况下,第二个获胜。 但是,如果我使用内存映射文件方法,则进程 1 将使用 4 字节的内存存储,然后是另一个 4 字节的内存存储(假设这不是最大内存存储大小)。过程 2 将做同样的事情。根据进程运行的时间,您可能会看到以下任何内容:
11111111
22222222
11112222
22221111
对此的解决方案是使用显式互斥——无论如何这可能是一个好主意。无论如何,在读/写文件 I/O 情况下,您有点依赖 O/S 来做“正确的事情”。
分类互斥原语是互斥体。对于内存映射文件,我建议您查看内存映射互斥锁,可以使用(例如)pthread_mutex_init()。
用一个陷阱进行编辑:当您使用映射文件时,很容易将指向文件中数据的指针嵌入文件本身(想想存储在映射文件中的链表)。您不想这样做,因为文件可能在不同时间或在不同进程中映射到不同的绝对地址。相反,使用映射文件中的偏移量。
并发将是一个问题。随机访问更容易性能好到好。便于使用。没那么好。便携性- 不那么热。
我很久以前在 Sun 系统上使用过它们,这些是我的想法。