mmap 用于写入顺序日志文件以提高速度?

mmap for writing sequential log file for speed?

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

我通常使用 mmap 来编写固定大小的结构,通常一次只写一页,但是这是用于使用 mmap 编写日志文件(0.5 - 10 Gb 的任何地方)但不确定什么是最好的练习第一个映射区域被填满后 - munmap,调整文件大小 truncatemmap 下一页 ?

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

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

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

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

Q3:我madvise是顺序访问吗?

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

这是为了 linux/c++/c 在 Mac 上进行选择性测试(所以没有重新映射 [?])。

任何 links/pointers 最佳实践表示赞赏。

我的学士论文是关于 fwrite VS mmap 的比较 ("An Experiment to Measure the Performance Trade-off between Traditional I/O and Memory-mapped Files")。首先,对于写入,您不必去寻找内存映射文件,尤其是大文件。 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" 机制。

取消映射

  • 第一次冲洗(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,但它说

This call is Linux-specific, and should not be used in programs intended to be portable.

广告 Q2:

我不确定我是否理解了这一点。如果你想告诉内核 "and now load the next page",那么不,这是不可能的(至少据我所知)。但是请参阅 Ad Q3 了解如何通知内核。

广告 Q3:

您可以使用带有标志 MADV_SEQUENTIALmadvise,但请记住,这不会强制内核提前读取,而只是建议它。

摘自 man:

This may cause the kernel to aggressively read-ahead

个人总结:

不要使用mmap进行顺序数据写入。与使用 fwrite.

的简单编写算法相比,它只会导致更多的开销并导致更多的 "unnatural" 代码

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

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

using mmap (for speed). What is the best procedure?

不要使用 mmap,请使用 write。严重地。为什么人们似乎总是认为 mmap 会神奇地加快速度?

创建 mmap 并不便宜,那些页表不会自行填充。当你想附加到一个文件时,你必须

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

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

为了进一步阐述,我将参考 Linus Torvalds 本人:

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

In article <200004042249.SAA06325@op.net>, Paul Barton-Davis wrote: >

I was very disheartened to find that on my system the mmap/mlock approach took 3 TIMES as long as the read solution. It seemed to me that mmap/mlock should be at least as fast as read. Comments are invited.

人们喜欢 mmap() 和其他使用页表的方法 优化掉一个复制操作,有时这是值得的。

HOWEVER,用虚拟内存映射玩游戏非常 本身就很贵。它有许多非常实际的缺点 人们往往会忽略,因为内存复制被视为非常重要的事情 缓慢,有时优化该副本被视为显而易见的 改进。

mmap 的缺点:

  • 相当可观的设置和拆卸成本。我的意思是 明显。这就像遵循页表来干净地取消所有映射。它是维护所有映射列表的簿记。这是取消映射后需要的 TLB 刷新。

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

mmap 的优点:

  • 如果数据被一遍又一遍地重复使用(在一个映射操作中),或者如果你可以通过映射一些东西来避免很多其他逻辑,mmap() 只是自切片面包以来最伟大的事情。这可能是一个你多次查看的文件(可执行文件的二进制映像是这里的明显情况 - 代码到处跳转),或者一个设置,它非常方便地映射整个东西而不考虑mmap() 刚刚获胜的实际使用模式。您可能有随机访问模式,并使用 mmap() 作为跟踪您实际需要的数据的方式。

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

而自动分享显然是这样的..

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

莱纳斯