使用 ofstream 进行缓冲文本输出以获得性能

Using ofstream for buffered text output to gain performance

本文关键字:性能 输出 文本 ofstream 缓冲 使用      更新时间:2023-10-16

我需要编写一个程序,该程序将在输出文件中写入许多字符。我的程序还需要编写换行符以获得更好的格式。我知道ofstream是一个缓冲流,如果我们对文件 io 使用缓冲流,我们会获得性能。但是,如果我们使用std::endl输出将被刷新,并且由于缓冲输出,我们将失去任何潜在的性能增益。

我想如果我将 'n' 用于新行,则只有在我们std::endl时才会刷新输出。这是对的吗?是否有任何技巧可用于在文件输出期间获得性能提升?

注意:我想在文件写入操作完成时刷新缓冲输出。我认为通过这种方式,我可以最大限度地减少文件 I/O,从而获得性能。

通常,如果需要最大性能,流类的用户不应弄乱流的刷新:流在缓冲区已满时在内部刷新其缓冲区。这实际上比等到所有输出准备就绪更有效,尤其是对于大文件:缓冲数据在可能仍在内存中时写入。如果您创建一个巨大的缓冲区并且只写入一次,则虚拟内存系统会将部分数据放入光盘而不是文件。它需要从光盘中读取并再次写入。

关于std::endl的要点是,人们滥用它作为行尾,导致缓冲区刷新,并且他们不知道性能影响。std::endl的目的是让人们控制在合理的点刷新文件。为了有效,他们需要知道自己在做什么。可悲的是,有太多人不知道std::endl做什么,他们宣传它的使用作为行尾,以至于它被用于许多明显错误的地方。

也就是说,以下是您可能想要尝试提高性能的一些内容。我假设您需要格式化输出(使用 std::ofstream::write() 不会给您(。

  • 显然,除非必须,否则不要使用std::endl。如果编写代码已存在并在许多位置使用std::endl(其中一些位置可能超出您的控制范围(,则可以使用筛选流缓冲区,该缓冲区使用其大小合理的内部缓冲区,并且不会将对其sync()函数的调用转发到基础流缓冲区。虽然这涉及额外的副本,但这比一些虚假刷新要好,因为这些刷新要贵几个数量级。
  • 尽管它不应该对std::ofstream产生影响,但调用std::ios_base::sync_with_stdio(false)用于影响某些实现的性能。如果这有影响,您需要考虑使用不同的 IOstream 实现,因为在性能方面可能存在更多问题。
  • 确保您使用的是std::locale,其std::codecvt<...>在调用其always_noconv()时返回true。这可以通过使用 std::use_facet<std::codecvt<char, char, stdd::mbstate_t> >(out.get_loc()).always_noconv() 轻松检查。您可以使用std::locale("C")来获取应该为真的std::locale
  • 某些区域设置实现使用非常低效的数值分面实现,即使它们相当好,std::num_put<char>分面的默认实现仍可能执行您并不真正需要的操作。特别是如果你的数字格式相当简单,即你不不断更改格式标志,你没有替换字符的映射(即你不使用有趣的std::ctype<char>分面(等,使用自定义std::num_put<char>方面可能是合理的:为整数类型创建一个快速但简单的格式化函数,为浮点创建一个很好的格式化函数是相当容易的,而浮点在内部不使用snprintf()

有些人建议使用内存映射文件,但这只有在事先知道目标文件的大小时才合理。如果是这种情况,这也是提高性能的好方法,否则不值得费心。请注意,您可以通过创建使用内存映射接口的自定义std::streambuf,将流格式化用于内存映射文件(或者更一般地说,与任何类型的输出接口一起使用(。我发现内存映射有时在将它们与std::istream一起使用时有效。在许多情况下,差异并不重要。

很久以前,我编写了自己的 IOStreams 和语言环境实现,它没有遇到上面提到的一些性能问题(它可以从我的网站上获得,但它有点陈旧,我已经有近 10 年没有碰过它了(。在这个实现中还有很多东西可以改进,但我还没有准备好在某处发布的最新实现。很快,希望 - 这是我近 10 年来一直在思考的事情,不过......

打印

n不会(不一定(刷新输出,而打印std::endlstd::flush会。

如果您想要快速写入并且不在乎数据是否在那里,直到您完全完成,那么请使用n进行所有写入,并且不必担心(因为关闭文件也会刷新流(。

如果你仍然没有得到你想要的性能,你可以使用 fstream::read(char*, int( - 它允许你读取你想要的任何大小的数据块(尝试更大的块,看看是否有帮助(。

是的

endl刷新流。不要将其用于大文件。

此外,请确保设置流缓冲区。至少在未设置缓冲区时,MSVC 实现一次将 1 个字符复制到filebuf(请参阅streambuf::xsputn(。这可能会使应用程序受 CPU 限制,从而导致较低的 I/O 速率。

因此,在编写之前,请在代码中添加类似以下内容:

char buf[256 * 1024];
mystream.rdbuf()->pubsetbuf(buf, sizeof(buf));

注意:您可以在此处找到完整的示例应用程序。