2009-12-29 74 views
13

我得到了一些我需要解析的大文件,而且人们一直在推荐mmap,因为这应该避免必须在内存中分配整个文件。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; 
} 
+0

@monkeyking,code-pre的正确关闭是/ pre/code,不是post :-)修复了代码标签。 – paxdiablo 2009-12-29 03:34:34

+0

啊,万分感谢! #include我不能把它们放到代码示例中 – monkeyking 2009-12-29 03:37:54

+0

标记整个块然后使用CTRL-K - 这会缩进四个空格。我现在已经这样做了,你应该可以看到一个stdio包含。 – paxdiablo 2009-12-29 03:44:06

回答

39

不,你在做什么是映射文件到内存中。这与将文件实际读入内存不同。

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

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

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

内存映射的主要优点是:

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

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

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

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

是的,但它可能不在物理一次记忆。操作系统会将位退回到文件系统,以便引入新的位。

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

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

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

这真的仅仅是一个窗口,文件:

                                        memory mapped file image

+0

只是为了澄清。所以如果我需要整个文件,mmap会实际加载整个文件? – monkeyking 2009-12-29 03:47:38

+0

是的,查看更新。 – paxdiablo 2009-12-29 04:01:52

+0

@paxdiablo,你能否澄清一下:“手动将文件读入内存时,操作系统会将部分地址空间(可能包含数据或不可用)交换到交换文件中”。如果我们读取(2)整个文件到内存 - >写(2)一些数据 - >关闭(2)它(fsync(2),如果需要的话)是否意味着该文件将不包含最新的更改? 还是应该使用下面的方案?读(2) - >一些更改 - >写(2)整个文件。 – dshil 2018-02-21 03:11:37

0

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

+0

错误。 VM将使用RAM来使文件可用;但只要存在一定的内存压力,它就会被换出。这几乎就像使用RAM作为文件的缓存一样。 – Javier 2009-12-29 03:39:08

+0

错了。它永远不会使用交换空间进行只读映射。它会做I/O来交换它,但你不会使用空间。 – bmargulies 2009-12-29 13:29:28

3

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

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

1

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

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

2

您可能被提供了错误的建议。

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

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

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

一般来说,我绝不会建议任何人使用mmap,除了一些极端的性能边缘情况 - 例如同时从多个进程或线程访问文件,或者文件与免费量有效内存。

+1

呃。您可以在使用mmap进行约10次树查找的过程中,逐块地预先生成B +树结构。 – 2010-07-15 20:37:13

+0

不一定正确。第一次读取的IO的性能将在mmap和pread之间几乎相同(对于所有实际目的而言) - 两者都必须从媒体中读取它。问题在于随后的读取。 Mmap将使用内核的内存驱逐LRU算法来决定映射哪些页面。通过Pread,IO子系统可以决定从缓存中删除哪些块(如果有的话)。在释放未使用的内存资源方面,这两种方法都不是高效的。因此,依赖于mmap的应用程序可能会通过占用内存资源来降低整个系统的性能和效率。 – tgiphil 2010-07-22 03:43:38

+1

您不计算每个系统调用浪费的几千个CPU周期。 mmap加载速度更快。 – 2011-07-11 21:58:55

0

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

如果您的解析只是单次传递文件,并且回看或向前看的次数最少,那么使用mmap代替标准库缓冲I/O,实际上并不会获得任何结果。在你用来计算文件中换行符的例子中,使用fread()这样做会更快。不过,我认为你的实际解析更复杂。

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

0

有点偏题。

我不完全同意马克的回答。其实mmapfread快。

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

恰恰相反,mmap只是返回指向系统缓冲区的指针。所以有一个双存储器拷贝节省

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

+0

我实际上已经完成了基准测试,结果显示(在Mac OS X上),窗口化mmap和fread之间的直通式读取吞吐量几乎没有差异。是的,使用高级库时,数据会被复制(最多三次),但与实际的I/O时间相比,复制数据的时间可以忽略不计。我通常使用适当的最高级别界面。 – 2010-01-02 20:44:28

+1

@Mark:在第一次阅读文件时与您同意。但是,如果程序不止一次读取文件,或者程序反复运行(例如Web服务器),则会有很大差异。 (将'fread'改为'mmap'使得我的经验中整个程序速度提高了50%) – iamamac 2010-01-03 05:00:46

+0

特别是当考虑到'fseek' +'fread'总是读取缓冲区的完整大小以获得任意大小。 – 2010-11-04 13:44:31

4

您也可以使用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标准1003.1,2004年和常量的先进的实时选项将POSIX_MADV_SEQUENTIAL和POSIX_FADV_SEQUENTIAL的一部分。