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