mmap问题,分配大量内存

时间:2009-12-29 03:31:50

标签: c++ c memory mmap

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

但是看看'top'看起来我确实打开了整个文件到内存中,所以我觉得我一定做错了。 'top shows> 2.1 gig'

这是一段代码片段,展示了我正在做的事情。

由于

#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;
}

8 个答案:

答案 0 :(得分:39)

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

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

这是您获得绩效的地方。如果您映射整个文件但只更改一个字节然后取消映射它,您将发现根本没有太多的磁盘I / O.

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

内存映射的主要优点是:

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

请注意,地址空间使用率与物理内存使用量之间很可能存在脱节。您可以在仅具有1G RAM的32位计算机中分配4G的地址空间(理想情况下,尽管可能存在操作系统,BIOS或硬件限制)。操作系统处理来往磁盘的分页。

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

  

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

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

但如果你手动阅读整个文件,它也会这样做。这两种情况之间的区别如下。

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

使用内存映射,您已经有效地告诉它将原始文件用作仅用于该文件/内存的额外交换区域 。并且,当数据写入 交换区域时,它会立即影响实际文件。因此,当你完成时不必手动重写任何东西,并且不会影响正常的交换(通常)。

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

memory mapped file image

答案 1 :(得分:4)

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

#include <sys/mman.h> 

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

建议在advice参数中指出,可以是

MADV_SEQUENTIAL 

按顺序排列页面引用。     (因此,可以提前读取给定范围内的页面,     并且可以在访问后立即释放。)

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

答案 2 :(得分:3)

top有许多与内存相关的列。它们中的大多数都是基于映射到进程的内存空间的大小;包括任何共享库,交换RAM和mmapped空间。

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

答案 3 :(得分:2)

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

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

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

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

一般情况下,我绝不会建议任何人使用mmap,除了一些极端的性能边缘情况 - 比如同时从多个进程或线程访问文件,或者当文件与可用的免费数量相关时很小存储器中。

答案 4 :(得分:1)

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

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

答案 5 :(得分:0)

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

答案 6 :(得分:0)

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

如果你的解析是单次传递文件,只有最小的回顾或前瞻,那么你实际上不会通过使用mmap而不是标准库缓冲I / O获得任何东西。在您计算文件中的换行符的示例中,使用fread()执行此操作的速度同样快。我假设您的实际解析更复杂。

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

答案 7 :(得分:0)

有点偏离主题。

我不完全同意马克的回答。实际上mmapfread更快。

尽管利用了系统的磁盘缓冲区,fread也有一个内部缓冲区,此外,数据将在调用时复制到用户提供的缓冲区。

相反,mmap只返回指向系统缓冲区的指针。所以有一个两个内存副本保存

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