【问题标题】:mmap( ) vs read( )映射()与读取()
【发布时间】:2011-04-07 22:59:06
【问题描述】:

我正在用 C 语言编写一个批量 ID3 标签编辑器。ID3 标签通常位于 mp3 编码文件的开头,尽管较旧的(版本 1)标签位于末尾。该应用程序旨在从命令行接受目录和框架 ID 列表,然后递归目录结构以更新它找到的所有 ID3 标记。用户还可以选择删除所有旧的(版本 1)标签。另一种选择是简单地显示当前标签,而不执行更新。该目录可能包含 2 个文件或 200 万个文件。如果用户打算更新文件,我打算将整个文件加载到内存中,执行更新,然后保存(文件也可能被重命名)。但是,如果用户只想打印当前的 ID3 标签,那么加载整个文件似乎是多余的。毕竟文件可能是200mb。

我已经阅读了这篇文章,很有见地 - mmap() vs. reading blocks

所以我的问题是,最有效的方法是什么——read()、mmap() 或某种组合?欢迎设计创意。

编辑:据我了解,mmap 本质上是将文件加载到内存中,委托给虚拟内存子系统。在我看来,VMM 将在大多数系统上进行高度优化,因为它对系统性能至关重要。

【问题讨论】:

  • FWIW,文件读取也非常优化,因为它对性能也很重要。 mmap() 对于随机访问往往稍快,read() 对于块/流访问往往更快(或至少足够快)。读写mmap() 将使就地更新标签变得更快、更容易,如果你想这样做的话——因为你只需要映射标签区域并且写入有效地直接到磁盘缓冲区。

标签: c mmap


【解决方案1】:

这真的取决于你想要做什么。如果你需要做的只是跳到一个已知的偏移量并读出一个小标签,read() 可能会更快(mmap() 必须做一些相当复杂的内部会计)。但是,如果您打算复制所有 200mb 的 MP3,或者扫描它以查找可能出现在未知偏移处的某些标签,那么mmap() 可能是一种更快的方法。

例如,如果您需要将整个文件向下移动几百字节以插入 ID3 标记,一种简单的方法是使用 ftruncate() 扩展文件,mmap 文件,然后 memmove()内容下降了一点。但是,如果您的程序在运行时崩溃,这将破坏文件。您还可以将文件的内容复制到一个新文件中——这是 mmap() 真正发挥作用的另一个地方;您可以简单地 mmap() 旧文件,然后使用单个 write() 将其所有数据复制到新文件中。

简而言之,如果您在传输的总字节数方面进行大量 IO,mmap() 非常棒;这是因为它减少了所需的副本数量,并且可以显着减少读取缓存数据所需的内核条目数量。然而mmap() 至少需要两次进入内核(如果您在完成后清理映射,则需要三次!)并执行一些复杂的内部内核记帐,因此固定开销可能很高。

另一方面,read() 涉及额外的内存到内存复制,因此对于大型 I/O 操作可能效率低下,但很简单,因此固定开销相对较低。简而言之,将 mmap() 用于大型批量 I/O,将 read()pread() 用于一次性小型 I/O。

【讨论】:

  • 嗯,我读到相反:使用read() 进行顺序批量访问,使用mmap() 进行随机访问小型访问。
  • @musiphil:您可能应该将其读为“将mmap 用于许多 小随机访问”——对于少数访问,可能不值得。
【解决方案2】:

不要打扰mmap,除非您的代码受 CPU 限制,特别是由于大量的小读写操作。 mmap 可能听起来不错,但这并不是很棒的原因,为什么不是每个人都在使用这个它看起来像的替代方案。

鉴于您正在通过潜在的大型目录结构进行递归,您的瓶颈将是目录 IO 和并发性。 mmap 无济于事。

更新0

阅读链接到的问题发现这个答案支持我的经验:

【讨论】:

    【解决方案3】:

    如果您通常不打算将文件流式传输然后处理它,而是跳来跳去(例如读取前面的标签然后跳到末尾等),那么我会使用 mmap 仅仅是因为您的代码将更干净,更容易维护,将文件视为一个大缓冲区,而无需自己实际管理缓冲和分页。

    如前所述,如果您正在处理大量数据,那么磁盘 I/O 很可能会主导您的处理。 mmap 可能比读取快,但对于合理的实施,它可能不会快多少,尤其是在当今的硬件上,它不断变得越来越快,而磁盘驱动器多年来一直停留在 7200 和 10,000 RPM。

    所以,使用 mmap 让您的代码简单而整洁。

    【讨论】:

      【解决方案4】:

      我不知道标准 POSIX 函数是否存在于您被允许或您将用于开发的范围内,但请考虑这两个函数:

      int ftruncate(int fildes, off_t length);
      int truncate(const char *path, off_t length);
      

      unistd.h 中定义,可用于将文件截断到指定长度。这样你就可以轻松

      • 找到 ID3 标记帧的开始位置(不知道是否可以通过读取 MP3 文件的标题轻松计算,但我猜是的)
      • 保存偏移量
      • 关闭文件
      • 使用提供的函数截断文件
      • 以追加二进制模式打开文件并写入新标签

      我不确定性能,你应该测试这个方法,但它应该在 ram 中加载更少的东西,同时提供一种有意义的方法。

      【讨论】:

      • 我知道截断,但是我更感兴趣的是 read() 和 mmap() 在我的特定场景中的性能对比(如果存在的话)。
      猜你喜欢
      • 1970-01-01
      • 2013-09-22
      • 1970-01-01
      • 2017-03-13
      • 2021-07-06
      • 2022-06-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多