什么是C ++中高性能顺序文件I / O的最快方法?

时间:2009-07-29 15:53:59

标签: c++ performance file-io

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

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


是否普遍接受了在C ++中实现最快的顺序文件I / O的指南?

一些可能的考虑因素:

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

我意识到这将有特定于平台的考虑因素。我欢迎一般准则以及特定平台的准则 (我对Win x64的最直接兴趣,但我也对Solaris和Linux上的评论感兴趣)

7 个答案:

答案 0 :(得分:31)

  

是否普遍接受了在C ++中实现最快的顺序文件I / O的指南?

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

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

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

  3. 一些可能的考虑因素:

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

    初学者的页面大小,但可以随时调整。

      
        
    • 像boost :: asio这样的可移植库是否过于抽象,无法揭示错综复杂的内容   特定平台,还是可以假设它们是最优的?
    •   

    不要认为它是最佳的。这取决于库在您的平台上的运行程度,以及开发人员为实现这一目标所付出的努力。虽然说便携式I / O库可以非常快,因为大多数系统都存在快速抽象,并且通常可以提出涵盖许多基础的通用API。 Boost.Asio就我所掌握的知识而言,对它所使用的特定平台进行了相当精细的调整:有一整套用于快速异步I / O的OS和OS变体特定API(例如epoll/dev/epollkqueueWindows overlapped I/O)和Asio将它们全部包裹起来。

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

    异步I / O在原始意义上并不比同步I / O快。异步I / O执行的操作是确保您的代码不会浪费时间等待I / O完成。它通常比不浪费时间的其他方法更快,即使用线程,因为它会在I / O准备就绪时而不是之前回调到代码中。没有错误启动或担心需要终止空闲线程。

答案 1 :(得分:12)

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

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

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

如果mmap()不符合您的喜好,那么就会有fadvise(),而对于非常棘手的问题,则会出现异步文件I / O.

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

答案 2 :(得分:7)

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

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

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

答案 3 :(得分:3)

如上所述,这一切都取决于您使用的机器/系统/库。一个系统的快速解决方案可能在另一个系统上很慢。

一般的指导方针是尽可能多地写入块。
通常一次写一个字节是最慢的。

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

答案 4 :(得分:2)

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

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

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

在* nixes上,您可能会在原始磁盘设备上顺序写入更高的性能。这在Windows上也是可行的,但API不支持。这样可以避免一点文件系统开销,但可能不足以使用。

简而言之,RAM比磁盘快1000倍或更快,而CPU速度更快。除非尽可能避免磁头(搜索)的移动,否则可能没有很多逻辑优化会有所帮助。仅适用于此文件的专用磁盘可在此处发挥重要作用。

答案 5 :(得分:2)

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

使用2的幂的缓冲区大小读取。只有基准测试可以确定这个数字。我曾经看过它是8K。另一次我发现它是8M!这种情况变化很大。

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

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

答案 6 :(得分:2)

在Linux上,缓冲的读取和写入会大大加快速度,随着缓冲区大小的增加越来越多,但回报正在减少,您通常希望将BUFSIZ(由stdio.h定义)更大缓冲区大小无济于事。

mmap ing可以最快地访问文件,但mmap调用本身相当昂贵。对于小文件(16KiB)readwrite系统调用获胜(有关阅读readmmap的数字,请参阅https://stackoverflow.com/a/39196499/1084774)。