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