【问题标题】:mmap problem, allocates huge amounts of memorymmap问题,分配大量内存
【发布时间】:2010-12-30 16:11:49
【问题描述】:

我有一些需要解析的大文件,人们一直在推荐 mmap,因为这样可以避免将整个文件分配到内存中。

但是看着“顶部”,它看起来确实像我将整个文件打开到内存中,所以我想我一定是做错了什么。 '顶级节目 >2.1 gig'

这是一个代码 sn-p,它显示了我在做什么。

谢谢

#include <stdio.h>
#include <stdlib.h>
#include <err.h>
#include <fcntl.h>
#include <sysexits.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <cstring>
int main (int argc, char *argv[] ) {
  struct stat sb;
  char *p,*q;
  //open filedescriptor
  int fd = open (argv[1], O_RDONLY);
  //initialize a stat for getting the filesize
  if (fstat (fd, &sb) == -1) {
    perror ("fstat");
    return 1;
  }
  //do the actual mmap, and keep pointer to the first element
  p =(char *) mmap (0, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
  q=p;
  //something went wrong
  if (p == MAP_FAILED) {
    perror ("mmap");
    return 1;
  }
  //lets just count the number of lines
  size_t numlines=0;
  while(*p++!='\0')
    if(*p=='\n')
      numlines++;
  fprintf(stderr,"numlines:%lu\n",numlines);
  //unmap it
  if (munmap (q, sb.st_size) == -1) {
    perror ("munmap");
    return 1;
  }
  if (close (fd) == -1) {
    perror ("close");
    return 1;
  }
  return 0;
}

【问题讨论】:

  • @monkeyking,code-pre 的正确关闭是 /pre-/code,而不是 post :-) 为您修复了代码标签。
  • 啊谢谢一百万! #include 怎么样,我无法将这些放入代码示例中
  • 标记整个块然后使用 CTRL-K - 这将缩进四个空格。我现在已经完成了,您应该能够看到一个 stdio 包含。

标签: c++ c memory mmap


【解决方案1】:

不,您正在做的是将文件映射到内存中。这与实际将文件读入内存不同。

如果您要读入它,则必须将整个内容传输到内存中。通过映射它,您可以让操作系统处理它。如果您尝试读取或写入该内存区域中的某个位置,操作系统将首先为您加载相关部分。除非需要整个文件,否则它不会加载整个文件。

这就是您获得性能提升的地方。如果你映射整个文件但只更改一个字节然后取消映射,你会发现根本没有多少磁盘 I/O。

当然,如果您触摸文件中的每个字节,那么是的,它会在某个时间点全部加载,但不一定一次全部加载到物理 RAM 中。但即使您预先加载整个文件也是如此。如果没有足够的物理内存来容纳所有数据,操作系统将换出您的部分数据,以及系统中其他进程的数据。

内存映射的主要优点是:

  • 您推迟读取文件部分,直到需要它们(并且,如果它们从不需要,它们就不会被加载)。因此,在加载整个文件时没有大笔的前期成本。它摊销了装载成本。
  • 写入是自动的,您不必写出每个字节。只需关闭它,操作系统就会写出更改的部分。我认为当内存被换出时也会发生这种情况(在物理内存不足的情况下),因为您的缓冲区只是文件的一个窗口。

请记住,地址空间使用量和物理内存使用量之间很可能存在脱节。您可以在只有 1G RAM 的 32 位机器中分配 4G 的地址空间(理想情况下,尽管可能存在操作系统、BIOS 或硬件限制)。操作系统处理与磁盘之间的分页。

并回答您的进一步澄清请求:

只是为了澄清。那么如果我需要整个文件,mmap 会实际加载整个文件吗?

是的,但它可能不会同时在物理内存中。操作系统会将位换回文件系统以引入新位。

但如果您手动读取了整个文件,它也会这样做。这两种情况的区别如下。

通过手动将文件读入内存,操作系统会将您的部分地址空间(可能包含数据,也可能不包含)交换到交换文件中。完成后,您将需要手动重写文件。

通过内存映射,您有效地告诉它使用原始文件作为额外的交换区域仅用于该文件/内存。而且,当数据写入那个交换区时,它会立即影响实际文件。因此,完成后无需手动重写任何内容,也不会影响正常的交换(通常)。

它实际上只是文件的一个窗口:

【讨论】:

  • 只是为了澄清。那么如果我需要整个文件,mmap 会实际加载整个文件吗?
  • @paxdiablo,您能否也澄清一下:“手动将文件读入内存后,操作系统会将您的部分地址空间(可能包含数据或可能不包含)交换到交换文件” .您的意思是如果我们将整个文件读(2)到内存中-> 写入(2)一些数据-> 关闭(2)它(如果需要,fsync(2))该文件将不包含最新的更改?还是应该使用以下方案?读取(2) -> 一些更改 -> 写入(2) 整个文件。
【解决方案2】:

您还可以使用 fadvise(2)(和 madvise(2),另请参见 posix_fadvise 和 posix_madvise )将 mmaped 文件(或其部分)标记为只读一次。

#include <sys/mman.h> 

int madvise(void *start, size_t length, int advice);

advice在advice参数中表示,可以是

MADV_SEQUENTIAL 

期望页面引用按顺序排列。 (因此,给定范围内的页面可以积极地提前阅读, 并且可能在它们被访问后很快被释放。)

便携性: posix_madvise 和 posix_fadvise 是 IEEE Std 1003.1, 2004 的 ADVANCED REALTIME 选项的一部分。常量将是 POSIX_MADV_SEQUENTIAL 和 POSIX_FADV_SEQUENTIAL。

【讨论】:

    【解决方案3】:

    top 有许多与内存相关的列。其中大部分是基于映射到进程的内存空间大小;包括任何共享库、换出的 RAM 和映射空间。

    检查RES 列,这与当前使用的物理RAM 有关。我认为(但不确定)它将包括用于“缓存”mmap'ped 文件的 RAM

    【讨论】:

      【解决方案4】:

      您可能得到了错误的建议。

      内存映射文件 (mmap) 在您解析它们时将使用越来越多的内存。当物理内存变低时,内核将根据其 LRU(最近最少使用)算法从物理内存中取消映射文件的部分。但 LRU 也是全球性的。 LRU 还可以强制其他进程将页面交换到磁盘,并减少磁盘缓存。这会对其他进程和整个系统的性能产生严重的负面影响。

      如果您是线性读取文件,例如计算行数,则 mmap 是一个不好的选择,因为它会在将内存释放回系统之前填充物理内存。最好使用一次在一个块中流式传输或读取的传统 I/O 方法。这样之后内存可以立即释放。

      如果您随机访问文件,mmap 是一个不错的选择。但这并不是最优的,因为您仍将依赖内核的通用 LRU 算法,但使用它比编写缓存机制更快。

      一般来说,我绝不会推荐任何人使用 mmap,除非是一些极端的性能边缘情况 - 例如同时从多个进程或线程访问文件,或者当文件相对于可用的免费数量而言较小时记忆。

      【讨论】:

      • 嗯。在逐块读取 B+树结构所需的时间内,您可以使用 mmap 进行大约 10 次树查找。
      • 不一定正确。 mmap 和 pread 之间的第一次读取 IO 的性能几乎相同(出于所有实际目的)——两者都必须从媒体中读取它。问题在于后续阅读。 Mmap 将使用内核的内存回收 LRU 算法来决定要映射出哪些页面。使用 Pread 将 IO 子系统决定从缓存中删除哪些块(如果有)。就释放未使用的内存资源而言,这两种方法都不是高效的。因此依赖于 mmap 的应用程序可能会因为占用内存资源而降低整个系统的性能和效率。
      • 您没有计算每个系统调用浪费的数千个 CPU 周期。 mmap 加载速度更快。
      • mmap 也调用系统调用,它就在幕后。处理器和内核捕获对未映射页面的内存访问(假设尚未加载)并调用必要的系统函数将页面放入内存。即使是高性能数据库也使用块访问而不是 mmap,因此它们可以控制和有效利用内存。
      • 一旦加载了 mmap 页面,系统就不再参与其中。而且“高性能”数据库并不是那么高性能。如果您想要高性能,请查看 memcache。而且它不使用块访问。
      【解决方案5】:

      “在内存中分配整个文件”将两个问题混为一谈。一是你分配了多少虚拟内存;另一个是文件的哪些部分从磁盘读取到内存中。在这里,您分配了足够的空间来包含整个文件。但是,只有您触摸的页面才会在磁盘上实际更改。而且,一旦您更新了 mmap 为您分配的内存中的字节,无论进程发生什么,它们都会正确更改。您可以通过使用 mmap 的“size”和“offset”参数一次只映射文件的一部分来分配更少的内存。然后,您必须自己通过映射和取消映射来管理文件中的窗口,也许在文件中移动窗口。分配一大块内存需要相当长的时间。这可能会给应用程序带来意想不到的延迟。如果您的进程已经是内存密集型的,则虚拟内存可能已经碎片化,并且在您要求时可能无法为大文件找到足够大的块。因此,可能有必要尽可能早地进行映射,或者使用一些策略来保持足够大的内存块可用,直到您需要它为止。

      但是,既然您指定需要解析文件,为什么不通过组织解析器来操作数据流来完全避免这种情况呢?那么您最需要的是一些前瞻和一些历史记录,而不是需要将文件的离散块映射到内存中。

      【讨论】:

        【解决方案6】:

        系统肯定会尝试将您的所有数据放入物理内存中。您将节省的是交换。

        【讨论】:

        • 错误。 VM 将使用 RAM 使文件可用;但是一旦有一些内存压力,它就会被换掉。这几乎就像使用 RAM 作为文件的缓存一样。
        • 错了。它永远不会将交换空间用于只读映射。它会做 I/O 来交换它,但你不会使用空间。
        【解决方案7】:

        如果您不希望将整个文件一次映射到内存中,则需要在 mmap 调用中指定小于文件总大小的大小。使用 offset 参数和较小的尺寸,您可以一次一张地映射到较大文件的“窗口”中。

        如果您的解析是单次遍历文件,且回溯或前瞻最少,那么使用 mmap 而不是标准库缓冲 I/O,您实际上不会获得任何收益。在您给出的计算文件中的换行符的示例中,使用 fread() 会同样快。不过,我假设您的实际解析更复杂。

        如果您需要一次读取文件的多个部分,则必须管理多个 mmap 区域,这很快就会变得复杂。

        【讨论】:

          【解决方案8】:

          有点跑题了。

          我不太同意马克的回答。实际上mmapfread 快。

          fread虽然利用了系统的磁盘缓冲区,但也有一个内部缓冲区,另外,数据会在调用时复制到用户提供的缓冲区中。

          相反,mmap 只是返回一个指向系统缓冲区的指针。所以有一个 two-memory-copies-saving

          但是使用mmap 有点危险。您必须确保指针永远不会离开文件,否则会出现段错误。而在这种情况下fread 只是返回零

          【讨论】:

          • 我实际上已经完成了基准测试,结果表明(无论如何,在 Mac OS X 上)窗口化 mmap 和 fread 之间的吞吐量几乎没有差异,用于直通式读取。是的,使用高级库,数据确实会被复制(最多 3 次),但是与实际 I/O 时间相比,复制数据的时间可以忽略不计。我通常使用合适的最高级别接口。
          • @Mark:第一次读取文件时同意你的看法。但是,如果程序多次读取文件,或者程序重复运行(例如 web 服务器),就会有很大的不同。 (根据我的经验,将 fread 更改为 mmap 使整个程序快了 50%)
          • 特别是考虑到fseek+fread 总是读取给定大小的缓冲区的完整大小。
          猜你喜欢
          • 1970-01-01
          • 2016-06-03
          • 1970-01-01
          • 2017-07-20
          • 1970-01-01
          • 2017-12-15
          • 1970-01-01
          • 2011-01-07
          • 2011-03-18
          相关资源
          最近更新 更多