mmap和内存使用

19

我正在编写一个程序,从网络接收大量数据(以不同大小的块为单位),处理并将其写入内存。由于某些数据块可能非常大,因此我的当前方法是限制所使用的缓冲区大小。如果一个块比最大缓冲区大小还要大,我会将数据写入临时文件,并稍后读取该文件的块来进行处理和永久存储。

我想知道是否可以改进这个方法。我已经阅读了一段时间有关mmap的内容,但不确定它是否能够帮助我。我的想法是使用mmap来读取临时文件。这样有帮助吗?我最关心的是,偶尔出现的大数据块不应该填满我的主内存,导致其他所有东西都被交换出去。

此外,您认为使用临时文件的方法有用吗?我应该这样做吗?或者,我应该相信linux内存管理器来为我完成这项工作?还是我应该完全采用其他方法?


“Big”有多大?最重要的是,它与计算机上可用的总RAM相比如何? - zwol
大文件有好几个千兆字节那么大。我有24G的内存,所以有些文件可以占用物理内存的四分之一甚至更多。 - Elektito
1
基本上,通过使用 mmap(),您会导致该内存由文件支持,而不是由交换(所谓的匿名内存)支持。在内存压力下,内核可能会更积极地回收支持文件的内存,而不是匿名内存,或者它可能反过来,我不知道。 - ninjalj
在内存压力下,内核可能会比匿名内存更积极地回收文件支持的内存,或者反过来,我不知道。那么它是哪一个呢?在内存压力下,内核是回收文件支持的内存还是交换更积极? - Gokul
3个回答

18

Mmap 可以在多方面帮助你,我将通过一些假想的例子来解释:

首先:假设你的应用程序使用了一个 100MB 的 malloc 内存块,却出现了内存不足的情况,其中有 50% 被交换到了磁盘交换空间中。这意味着操作系统必须将 50MB 的数据写入到交换文件中,并且如果你需要再次读取它们,则会先将其写入磁盘并占用空间,然后再进行读取。

如果内存是通过 mmap 映射的,那么操作系统就不会将该信息写入到交换文件中(因为它知道该数据与文件本身相同),而是只会清除 50MB 的信息(假设现在还没有写入任何内容),然后完成操作。如果你需要再次读取该内存,操作系统将从你映射的原始文件中获取内容,而不是从交换文件中获取。因此,如果其他程序需要 50MB 的交换空间,那么它们就可以使用了。此外,没有任何与交换文件操作相关的开销。

假设你读取了一个 100MB 的数据块,并且根据初始的 1MB 头部数据,你想要的信息位于偏移量为 75MB 的位置,那么你不需要 1~74.9MB 之间的任何数据!你只是为了让你的代码更简单而读取它。使用 mmap,你只会读取你实际访问过的数据(舍入到 4kb 或操作系统页面大小,通常为 4kb),因此它只会读取第一个和第75MB。我认为很难找到比 mmap 文件更简单有效地避免磁盘读写的方法了。

如果由于某种原因你需要在偏移量为 37MB 的位置上使用数据,那么你可以直接使用它!你不需要再次进行 mmap,因为整个文件都可以在内存中访问(当然受制于你的进程内存空间的限制)。

所有mmap映射的文件都由它们自己支持,而不是由交换文件支持。交换文件用于授予没有文件支持的数据,这通常是malloc的数据或由文件支持但已被修改且在程序通过msync调用之前不能或不应写回该文件的数据。

请注意,您不需要将整个文件映射到内存中,您可以映射任何数量(第二个参数为“size_t length”),从任何位置开始(第六个参数为“off_t offset”)。但是,除非您的文件可能非常巨大,否则即使系统只拥有64mb物理内存,也可以安全地映射1GB的数据,但如果您计划进行写入,则应更加保守,并仅映射所需的内容。

映射文件将帮助您使代码更简单(您已经在内存中准备好使用文件内容,具有更少的内存开销,因为它不是匿名内存),并且更快(您只会读取您的程序访问的数据)。


谢谢。了解这些信息很好,但不幸的是大部分都不适用于我的当前情况。 - Elektito

3
使用mmap处理大文件的主要优势在于可以在两个或多个文件之间共享同一内存映射:如果使用MAP_SHARED进行mmap,则只会为将使用内存数据的所有进程加载一次,从而节省内存。 但据我所知,如果使用mmap对整个文件进行映射(您可以在此处找到mmap无法处理物理内存+交换空间大小以下的大文件的示例),因此如果仅从单个进程访问文件,则无法帮助您节省物理内存消耗。

那么,我还有其他方法可以确保文件不会全部加载到内存中吗?你看,我还有另一个问题。我需要将数据发送到MongoDB进行存储。现在Mongo需要我提供对某个内存缓冲区的指针,因此似乎无论是自己加载文件还是使用mmap,该文件都将在一定时间内完全存储在内存中。 - Elektito
2
我不熟悉MongoDB,但如果它需要一个包含整个文件的内存缓冲区,那么在使用临时文件时似乎没有任何意义。如果从网络直接读取到内存缓冲区,然后将其传递给MongoDB时的行为是不可接受的,我认为您将不得不在数据库中将大文件分成块。 - zwol
4
mmap 确实会将整个文件“映射到内存中”,但不会从磁盘读取文件到内存。如果使用特定标志或在非常特殊的内核配置下(这些配置不常用),或者尝试映射比物理内存+交换空间更大的文件时,可能会失败。在 32 位系统上,虚拟内存耗尽是真正的威胁,但只要正确使用,其他任何情况都不应该导致 mmap 失败。 - user1643723

1

我相信mmap不需要所有数据在同一时刻都在内存中 - 它使用页面缓存将最近使用的页面保留在内存中,其余部分则在磁盘上。

如果您一次只读取一个块,则使用临时文件可能不会有所帮助,但如果您同时使用多个线程、进程或使用select/poll读取多个块,则可能会有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接