我得到了一些需要解析的大文件,人们一直在推荐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;
}
答案 0 :(得分:39)
不,你正在做的是将文件映射到内存中。这与将文件实际读入内存有所不同。
如果您要阅读它,则必须将整个内容传输到内存中。通过映射,您可以让操作系统处理它。如果您尝试读取或写入该存储区中的某个位置,操作系统将首先为您加载相关部分。除非需要整个文件,否则不将加载整个文件。
这是您获得绩效的地方。如果您映射整个文件但只更改一个字节然后取消映射它,您将发现根本没有太多的磁盘I / O.
当然,如果你触摸文件中的每个字节,那么是的,它将在某个时刻加载,但不一定在物理RAM中同时加载。但即使您预先加载整个文件也是如此。如果没有足够的物理内存来包含所有数据,操作系统将交换部分数据,以及系统中其他进程的内存。
内存映射的主要优点是:
请注意,地址空间使用率与物理内存使用量之间很可能存在脱节。您可以在仅具有1G RAM的32位计算机中分配4G的地址空间(理想情况下,尽管可能存在操作系统,BIOS或硬件限制)。操作系统处理来往磁盘的分页。
并回答您的进一步澄清要求:
只是澄清一下。那么如果我需要整个文件,mmap实际上会加载整个文件吗?
是的,但它可能不会同时存在于物理内存中。操作系统会将位交换回文件系统,以便引入新位。
但如果你手动阅读整个文件,它也会这样做。这两种情况之间的区别如下。
将文件手动读入内存后,操作系统会将部分地址空间(可能包括数据或可能不包括)交换到交换文件中。完成后你需要手动重写文件。
使用内存映射,您已经有效地告诉它将原始文件用作仅用于该文件/内存的额外交换区域 。并且,当数据写入 交换区域时,它会立即影响实际文件。因此,当你完成时不必手动重写任何东西,并且不会影响正常的交换(通常)。
它实际上只是文件的一个窗口:
答案 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)
有点偏离主题。
我不完全同意马克的回答。实际上mmap
比fread
更快。
尽管利用了系统的磁盘缓冲区,fread
也有一个内部缓冲区,此外,数据将在调用时复制到用户提供的缓冲区。
相反,mmap
只返回指向系统缓冲区的指针。所以有一个两个内存副本保存。
但使用mmap
有点危险。您必须确保指针永远不会超出文件,否则会出现段错误。在这种情况下,fread
仅返回零。