【问题标题】:mmap for writing sequential log file for speed?mmap 用于写入顺序日志文件以提高速度?
【发布时间】:2016-03-09 12:23:58
【问题描述】:

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

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

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

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

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

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

Q3:我是否madvise 用于顺序访问?

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

这适用于 linux/c++/c 可选地在 Mac 上进行测试(因此无需重新映射 [?])。

任何指向最佳实践的链接/指针表示赞赏。

【问题讨论】:

    标签: c++ c linux mmap


    【解决方案1】:

    我写了关于 fwrite 与 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
    }
    

    广告第一季度:

    我使用了“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,但它声明

    此调用是特定于 Linux 的,不应在程序中使用 旨在便携。

    广告第二季度:

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

    广告第三季度:

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

    摘自man:

    可能导致内核主动预读

    个人结论:

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

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

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

    【讨论】:

    • mmap 确实没有中间缓冲区,并根据请求直接写入数据(write 和朋友也是如此),用户空间 API 通常会避免这种情况。这也允许更少的系统调用,这是昂贵的。
    • 您能否提供mmapfwrite 以及write 的基准数据?
    • 我只比较了mmapfwrite,还有并行化和侧载等参数,但论文目前还没有完全完成和发表,所以我不确定我是否被允许立即发布结果。
    • Markus 已经过去了一段时间。你现在可以分享你的结果了吗?具体来说,我很好奇 fwrite 如何更快。为什么 mmap 比较慢。我唯一的猜测是我从未使用过 msync()。我只是 mmap(),写入数据,munmap(),数据在它自己的好时机出现在磁盘上。仅通过不执行 msync() 或类似操作,fwrite() 是否更快?
    • 使用 mmap 的原因可能不仅仅是 IO 速度。例如,如果您的(可能是多线程的)程序在写入过程中崩溃,日志消息就会丢失。如果 OOM 终止您的进程,则日志消息将丢失。如果您忘记使用 append-only,则整个文件可能会出现乱码。另外,您必须同步。使用内存映射,您无需担心(您的示例中不需要很多同步)。您有“一些内存区域”可以写入,之后发生的事情不再是您的问题。当然,页面可能不会立即写出,但这很好(甚至是可取的)。
    【解决方案2】:

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

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

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

    • 截断到新的大小(使用实际上相当便宜的现代文件系统)
    • 取消映射旧映射(保留可能需要或不需要写出的脏页)
    • mmap 新映射,这需要填充页表。此外,每次写入之前没有错误的页面时,您都会调用页面错误处理程序。

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

    我将参考 Linus Torvalds 本人的详细说明:

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

    在文章 中,Paul Barton-Davis 写道: >

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

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

    但是,使用虚拟内存映射玩游戏非常 本身就很贵。它有许多非常现实的缺点 人们倾向于忽略,因为记忆复制被视为非常 缓慢,有时优化该副本被视为一个明显的 改进。

    mmap 的缺点:

    • 相当可观的安装和拆卸成本。我的意思是引人注目。就像按照页表干净地取消映射所有内容一样。这是维护所有映射列表的簿记。这是取消映射后需要的 TLB 刷新。

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

    mmap 的优点:

      1234563切片面包。这可能是您多次检查的文件(可执行文件的二进制映像在这里很明显 - 代码到处跳转),或者是一个设置,它可以方便地映射整个东西而不考虑mmap() 刚刚获胜的实际使用模式。您可能有随机访问模式,并使用 mmap() 作为跟踪您实际需要的数据的一种方式。
    • 如果数据很大,mmap() 是让系统知道它可以对数据集做什么的好方法。内核可能会忘记页面,因为内存压力会迫使系统将内容分页,然后自动重新获取它们。

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

    但是您的测试套件(仅复制一次数据)可能很糟糕 对于 mmap()。

    莱纳斯

    【讨论】:

    • 最近奇怪的结果将 mmap() reads 视为 matchingsurpassing read() 性能在大文件上。所以在某些情况下它可能会有所帮助。
    • @sourcejedi:链接?他们使用了哪个块大小?另见eklitzke.org/efficient-file-copying-on-linux
    • 64K 在链接 1 中。ripgrep(链接 2)目前似乎使用 8K。好点子,谢谢。您的链接很困惑,因为它根据预读来解释图表,但图表数据是针对完全虚拟(内存中)设备 /dev/zero。
    猜你喜欢
    • 2012-12-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    • 2015-10-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多