用于编写顺序日志文件以获得速度的mmap?

时间:2016-03-09 12:23:58

标签: c++ c linux mmap

我想使用mmap(速度)来编写日志文件,非结构化格式(一次一行)。什么是最好的程序?我是否将空文件truncate打开为1页大小(写入空字符串以调整文件大小?),然后mmap - 并在mmaped区域已满时重复?

我通常使用mmap来编写固定大小的结构,通常一次只有一页,但是这是用于使用mmap编写日志文件(任何地方0.5到10 Gb)但不确定什么是最佳实践一次第一个mmaped区域已填充 - munmap,调整文件truncatemmap下一页?

在将日志写入内存区域时,我会跟踪大小,msync,一旦到达映射内存区域的末尾,正确的处理是什么?

假设我永远不需要返回或覆盖现有数据,因此我只会将新数据写入文件。

问题1:当我到达映射区域的末尾时,我munmapftruncate文件是否会调整另一个页面大小并mmap下一页?

Q2:有没有一种标准的方法可以抢占并让下一页准备好在内存中进行下一次写入?当我们接近映射区域的末尾时,在另一个线程上执行此操作吗?

问题3:我是否madvise进行顺序访问?

这是用于保存日志文件的实时数据处理 - 目前我只是写入文件。日志文件是非结构化的,文本格式,基于行。

这适用于linux / c ++ / c,可选择在Mac上进行测试(所以没有重映射[?])。

赞赏最佳做法的任何链接/指示。

2 个答案:

答案 0 :(得分:19)

我写了关于fwrite VS mmap的比较的学士论文(“测量传统I / O和内存映射文件之间性能权衡的实验”)。首先,对于写入,您不必使用内存映射文件,特别是大文件。 fwrite完全没问题,并且几乎总是优于使用mmap的方法。 mmap将为您提供并行数据读取的最大性能提升;对于顺序数据写入,fwrite的真正限制是您的硬件。

在我的示例中,remapSize是文件的初始大小以及每次重新映射时文件增加的大小。 fileSize跟踪文件的大小,mappedSpace表示当前mmap的大小(它的长度),alreadyWrittenBytes是已经写入文件的字节。

以下是初始化的示例:

void init() {
  fileDescriptor = open(outputPath, O_RDWR | O_CREAT | O_TRUNC, (mode_t) 0600); // Open file
  result = ftruncate(fileDescriptor, remapSize); // Init size
  fsync(fileDescriptor); // Flush
  memoryMappedFile = (char*) mmap64(0, remapSize, PROT_WRITE, MAP_SHARED, fileDescriptor, 0); // Create mmap
  fileSize = remapSize; // Store mapped size
  mappedSpace = remapSize; // Store mapped size
}

广告Q1:

我使用了“Unmap-Remap”-mechanism。

取消映射

  • 首先冲洗(msync
  • 然后取消映射内存映射文件。

这看起来如下:

void unmap() {
  msync(memoryMappedFile, mappedSpace, MS_SYNC); // Flush
  munmap(memoryMappedFile, mappedSpace)
}

对于重映射,您可以选择重新映射整个文件或仅重新映射新添加的部分。

重新映射

  • 增加文件大小
  • 创建新的记忆地图

完全重映射的示例实现:

void fullRemap() {
  ftruncate(fileDescriptor, mappedSpace + remapSize); // Make file bigger
  fsync(fileDescriptor); // Flush file
  memoryMappedFile = (char*) mmap64(0, mappedSpace + remapSize, PROT_WRITE, MAP_SHARED, fileDescriptor, 0); // Create new mapping on the bigger file
  fileSize += reampSize;
  mappedSpace += remapSize; // Set mappedSpace to new size
}

小重映射的示例实现:

void smallRemap() {
  ftruncate(fileDescriptor, fileSize + remapSize); // Make file bigger
  fsync(fileDescriptor); // Flush file
  remapAt = alreadyWrittenBytes % pageSize == 0 
            ? alreadyWrittenBytes 
            : alreadyWrittenBytes - (alreadyWrittenBytes % pageSize); // Adjust remap location to pagesize
  memoryMappedFile = (char*) mmap64(0, fileSize + remapSize - remapAt, PROT_WRITE, MAP_SHARED, fileDescriptor, remapAt); // Create memory-map
  fileSize += remapSize;
  mappedSpace = fileSize - remapAt;
}

那里有一个mremap function,但它说明了

  

此调用是特定于Linux的,不应在程序中使用          打算携带。

广告Q2:

我不确定我是否明白这一点。如果你想告诉内核“现在加载下一页”,那么不,这是不可能的(至少据我所知)。但请参阅 Ad Q3 ,了解如何建议内核。

广告问题3:

您可以将madvise与标记MADV_SEQUENTIAL一起使用,但请记住,这不会强制内核提前读取,但只会建议它。

摘自man

  

可能导致内核积极预读

个人结论:

不要使用mmap进行顺序数据写入。它只会导致更多的开销,并且会导致比使用fwrite的简单编写算法更加“不自然”的代码。

使用mmap对大文件进行随机访问读取。

这也是我论文期间获得的结果。通过使用mmap进行顺序写入,我无法实现任何加速,实际上,为此目的它总是较慢。

答案 1 :(得分:3)

  

使用mmap(速度)。什么是最好的程序?

请勿使用mmap,请使用write。认真。为什么人们似乎总是认为mmap会以某种方式神奇地加快速度?

创建mmap并不便宜,这些页面表不会自己填充。如果要附加到文件,则必须

  • 截断到新的大小(实际上,现代文件系统非常便宜)
  • 取消映射旧映射(留下可能或可能不必写出的脏页)
  • mmap新映射,需要填充页表。此外,每次写入以前未默认的页面时,您都会调用页面错误处理程序。

mmap有一些很好的用途,例如在大型数据集中进行随机访问读取或从同一数据集中进行循环读取时。

为了进一步阐述,我将自己提到Linus Torvalds:

http://lkml.iu.edu/hypermail/linux/kernel/0004.0/0728.html

  

文章< 200004042249.SAA06325@op.net> ;, Paul Barton-Davis    中写道:   >

     
    

我非常沮丧地在我的系统上发现了mmap / mlock     只要读取解决方案,方法就会花费 3 TIMES 。在我看来     mmap / mlock应至少与读取速度一样快。评论是     邀请。

  
     

人们喜欢用mmap()和其他方式来玩页表   优化复制操作,有时值得。

     

但是,使用虚拟内存映射玩游戏非常有用   本身很贵。它有许多非常不利的缺点   人们往往会忽视,因为记忆复制被认为是非常重要的东西   缓慢,有时优化该副本被视为一个显而易见的   改良效果。

     

下行到mmap:

     
      
  • 非常明显的设置和拆卸成本。我的意思是明显的。它就像跟随页面表一样干净地取消映射所有内容。它是用于维护所有映射列表的簿记。它是取消映射后需要的TLB刷新。

  •   
  • 页面错误是昂贵的。这就是映射的填充方式,而且速度很慢。

  •   
     

mmap的好处:

     
      
  • 如果数据被反复重复使用(在单个地图操作中),或者如果你可以通过映射内容来避免很多其他逻辑,那么mmap()就是最好的事情。切片面包。这可能是你多次访问的文件(可执行文件的二进制图像在这里是明显的例子 - 代码在各处跳转),或者设置它可以方便地映射整个事物在不考虑mmap()刚刚获胜的实际使用模式的情况下。您可能拥有随机访问模式,并使用mmap()作为跟踪实际需要的数据的方式。

  •   
  • 如果数据很大,mmap()是让系统知道它可以用数据集做什么的好方法。内核可以忘记页面,因为内存压力迫使系统将页面输出,然后再自动重新获取它们。

  •   
     

自动共享显然就是这种情况。

     

但是你的测试套件(只是复制一次数据)可能是悲观的   对于mmap()。

     

莱纳斯