一尘不染

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

linux

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

我通常使用mmap在一个时间写固定大小的结构,通常只有一个页面,但是这是(从0.5的任何地方- 10
Gb)的写入日志文件使用mmap,但不知道什么是最好的做法,一旦第一mmaped区域充满-
munmap,调整文件大小truncatemmap下一页?

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

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

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

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

Q3:我madvise是否可以顺序访问?

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

这是针对Linux / c ++ / c(可选)在Mac上进行的测试(因此没有重新映射[?])。

任何有关最佳做法的链接/指标都值得赞赏。


阅读 1141

收藏
2020-06-07

共1个答案

一尘不染

我写了关于fwrite VS mmap( “衡量传统I / O与内存映射文件之间的性能折衷的实验”)
的比较学士学位论文。首先,对于写入,您不必去寻找内存映射文件,尤其是大文件。fwrite是完全可以的,并且几乎总是会优于使用mmapmmap将为您提供最大的并行数据读取性能提升;用于连续数据写入的真正限制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:

我使用了“取消映射-重新映射”机制。

取消映射

  • 第一次冲洗(msync
  • 然后取消映射内存映射的文件。

这看起来可能如下:

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

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

基本上重新映射

  • 增加文件大小
  • 创建新的内存映射

完整重映射的示例实现:

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:

我不确定我是否明白这一点。如果您想告诉内核“现在加载下一页”,则不行,这是不可能的(至少据我所知)。但是请参阅 广告第3步 ,了解如何建议内核。

广告Q3:

您可以madvise与flag一起使用MADV_SEQUENTIAL,但请记住,这不会强制内核提前读取,而只是建议您这样做。

男子摘录:

可能会 导致内核主动预读

个人结论:

请勿mmap用于顺序数据写入。与使用编写简单的算法相比,它只会导致更多的开销,并且会导致更多的“非自然”代码fwrite

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

这也是我论文期间获得的结果。通过使用mmap顺序写入,我无法实现任何提速,实际上,这样做总是较慢。

2020-06-07