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