17

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

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

这是一个代码片段,显示了我在做什么。

谢谢

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

8 回答 8

41

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

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

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

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

内存映射的主要优点是:

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

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

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

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

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

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

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

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

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

                        内存映射文件映像

于 2009-12-29T03:35:31.453 回答
5

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

#include <sys/mman.h> 

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

建议在建议参数中指示,可以是

MADV_SEQUENTIAL 

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

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

于 2010-02-07T02:58:55.760 回答
3

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

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

于 2009-12-29T03:36:47.753 回答
2

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

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

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

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

一般来说,我永远不会推荐任何人使用 mmap,除了一些极端的性能边缘情况 - 比如同时从多个进程或线程访问文件,或者当文件相对于可用内存量较小时。

于 2009-12-29T04:19:44.347 回答
1

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

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

于 2009-12-29T04:16:35.193 回答
0

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

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

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

于 2009-12-29T04:20:44.280 回答
0

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

于 2009-12-29T03:35:42.997 回答
0

有点跑题了。

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

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

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

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

于 2009-12-29T04:55:04.323 回答