【问题标题】:What is the Fastest Method for High Performance Sequential File I/O in C++?C++ 中高性能顺序文件 I/O 的最快方法是什么?
【发布时间】:2009-07-29 15:53:59
【问题描述】:

假设以下为...
输出:
文件已打开...
数据“流式传输”到磁盘。内存中的数据位于一个大的连续缓冲区中。它直接从该缓冲区以原始形式写入磁盘。缓冲区的大小是可配置的,但在流的持续时间内是固定的。缓冲区一个接一个地写入文件。不执行任何查找操作。
...文件已关闭。

输入:
从磁盘从头到尾读取一个大文件(按上述顺序写入)。


在 C++ 中实现最快的顺序文件 I/O 是否有普遍接受的准则?

一些可能的考虑:

  • 选择最佳缓冲区大小的指南
  • 像 boost::asio 这样的可移植库是否过于抽象而无法暴露特定平台的复杂性,或者它们是否可以被认为是最佳的?
  • 异步 I/O 总是优于同步吗?如果应用程序不受 CPU 限制怎么办?

我意识到这将有特定于平台的考虑。我欢迎通用指南以及特定平台的指南。
(我对 Win x64 最直接的兴趣,但我也对 Solaris 和 Linux 上的 cmets 感兴趣)

【问题讨论】:

  • 你想重新实现cp 吗?我想我错过了什么......

标签: c++ performance file-io


【解决方案1】:

在 C++ 中实现最快的顺序文件 I/O 是否有普遍接受的准则?

规则 0:衡量。使用所有可用的分析工具并了解它们。这几乎是编程中的一条诫命,如果你不测量它,你就不会知道它有多快,而对于 I/O,这更是如此。如果可能,请确保在实际工作条件下进行测试。对 I/O 系统没有竞争的进程可以过度优化,针对实际负载下不存在的条件进行微调。

  1. 使用映射内存而不是写入文件。这并不总是更快,但它允许以操作系统特定但相对可移植的方式优化 I/O,避免不必要的复制,并利用操作系统对磁盘实际使用方式的了解。 (“便携”,如果您使用包装器,而不是特定于操作系统的 API 调用)。

  2. 尽量使输出线性化。在优化条件下,必须在内存中跳转以找到要写入的缓冲区可能会产生明显的影响,因为缓存行、分页和其他内存子系统问题将开始变得重要。如果您有很多缓冲区,请查看对 scatter-gather I/O 的支持,它会尝试为您进行线性化。

一些可能的考虑:

  • 选择最佳缓冲区大小的指南

初学者的页面大小,但准备好从那里调整。

  • 像 boost::asio 这样的可移植库是否过于抽象而无法暴露复杂性 是针对特定平台的,还是可以假定它们是最优的?

不要认为它是最优的。这取决于库在您的平台上的使用程度,以及开发人员为使其快速运行付出了多少努力。话虽如此,可移植 I/O 库可以非常快,因为大多数系统上都存在快速抽象,而且通常可以提出涵盖许多基础的通用 API。据我所知,Boost.Asio 已经针对它所在的特定平台进行了相当精细的调整:有一整套用于快速异步 I/O 的操作系统和操作系统变体特定 API(例如 epoll、@987654322 @、kqueueWindows overlapped I/O),而 Asio 将它们全部包装起来。

  • 异步 I/O 总是优于同步吗?如果应用程序不受 CPU 限制怎么办?

在原始意义上,异步 I/O 并不比同步 I/O 快。异步 I/O 所做的是确保您的代码不会浪费时间等待 I/O 完成。一般而言,它比不浪费时间的其他方法(即使用线程)更快,因为它会在 I/O 准备好而不是之前回调到您的代码中。没有错误的启动或需要终止空闲线程的问题。

【讨论】:

    【解决方案2】:

    一般建议是关闭缓冲和大块读/写(但不要太大,否则您将浪费太多时间等待整个 I/O 完成,否则您可能会一开始就开始咀嚼已经是兆字节了。用这个算法找到最佳位置是微不足道的,只有一个旋钮可以转动:块大小)。

    除此之外,对于输入mmap()ing,共享和只读文件是(如果不是最快的话)最有效的方式。如果您的平台有,请调用madvise(),告诉内核您将如何遍历文件,以便它可以进行预读并在之后再次快速丢弃页面。

    对于输出,如果您已经有一个缓冲区,请考虑使用文件(也可以使用 mmap())支持它,这样您就不必在用户空间中复制数据。

    如果您不喜欢 mmap(),那么还有 fadvise(),而且,对于真正困难的,异步文件 I/O。

    (以上都是POSIX,Windows名称可能不同)。

    【讨论】:

    • 修复:fadvise(2) 和 madvise(2)。 posix 版本也被命名为 posix_fadvise 和 posix_madvise
    • mmap()ing 输出文件如何让您避免复制用户空间中的数据?
    【解决方案3】:

    对于 Windows,如果您选择使用特定于平台的 Windows API 调用,则需要确保在 CreateFile() 调用中使用 FILE_FLAG_SEQUENTIAL_SCAN。这将优化 I/O 的缓存。就缓冲区大小而言,通常建议缓冲区大小是磁盘扇区大小的倍数。 8K 是一个不错的起点,扩大后几乎没有什么收获。

    本文讨论了 Windows 上异步和同步之间的比较。

    http://msdn.microsoft.com/en-us/library/aa365683(VS.85).aspx

    【讨论】:

      【解决方案4】:

      正如您在上面提到的,这完全取决于您使用的机器/系统/库。一个系统上的快速解决方案在另一个系统上可能会很慢。

      不过,一般准则是写入尽可能大的块。
      通常一次写入一个字节是最慢的。

      最好的确定方法是编写几种不同的方法并对其进行分析。

      【讨论】:

        【解决方案5】:

        您询问了 C++,但听起来您已经过去了,准备了解一些特定于平台的内容。

        在 Windows 上,带有文件映射的FILE_FLAG_SEQUENTIAL_SCAN 可能是最快的方法。事实上,您的进程可以在文件实际进入磁盘之前退出。如果没有显式阻塞刷新操作,Windows 最多可能需要 5 分钟才能开始写入这些页面。

        如果文件不在本地设备上而是在网络驱动器上,您需要小心。网络错误将显示为 SEH 错误,您需要做好处理的准备。

        在 *nixes 上,顺序写入原始磁盘设备可能会获得更高的性能。这在 Windows 上也是可能的,但 API 不太支持。这将避免一点文件系统开销,但它可能还不够有用。

        简单地说,RAM 比磁盘快 1000 倍或更多倍,而 CPU 更快。除了尽可能避免磁头移动(寻道)之外,可能没有很多逻辑优化会有所帮助。仅用于此文件的专用磁盘在这里可以提供很大帮助。

        【讨论】:

        • posix 具有兼容的 posix_fadvise 调用和 POSIX_FADV_SEQUENTIAL 标志。
        【解决方案6】:

        使用CreateFileReadFile,您将获得绝对最快的性能。使用FILE_FLAG_SEQUENTIAL_SCAN 打开文件。

        读取的缓冲区大小是 2 的幂。只有基准测试才能确定这个数字。我曾经看过它是8K。还有一次我发现它是8M!这变化很大。

        这取决于 CPU 缓存的大小、操作系统预读的效率以及与执行许多小写操作相关的开销。

        内存映射不是最快的方法。它有更多的开销,因为您无法控制块大小并且操作系统需要在所有页面中出错。

        【讨论】:

          【解决方案7】:

          在 Linux 上,缓冲读取和写入会大大加快速度,随着缓冲区大小的增加而增加,但回报正在减少,您通常希望使用 BUFSIZ(由 stdio.h 定义),因为更大的缓冲区大小不会'帮助不大。

          mmaping 提供了最快的文件访问,但mmap 调用本身相当昂贵。对于小文件 (16KiB) readwrite 系统调用 win(请参阅 https://stackoverflow.com/a/39196499/1084774 以了解通过 readmmap 读取的数字)。

          【讨论】:

            猜你喜欢
            • 2013-07-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-05-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多