如何使用boost::iostreams更改缓冲区大小?

6

我的程序并行读取几十个非常大的文件,每次只读取一行。看起来主要的性能瓶颈是从一个文件到另一个文件的HDD寻道时间(虽然我不完全确定如何验证这一点),所以我认为如果我可以缓冲输入,那么速度会更快。

我使用像这样的C ++代码通过boost :: iostreams“过滤流”读取文件:

input = new filtering_istream;
input->push(gzip_decompressor());
file_source in (fname);
input->push(in);

根据文档file_source没有设置缓冲区大小的方法,但是filtering_stream::push似乎可以实现此功能。
void push( const T& t,
  std::streamsize buffer_size,
  std::streamsize pback_size );

所以我尝试了input->push(in, 1E9),我的程序的内存使用量确实飙升了,但速度并没有改变。我错了,读取缓冲会提高性能吗?还是我做错了?我可以直接缓冲file_source,还是需要创建一个filtering_streambuf?如果是后者,那该怎么办?文档中并没有丰富的例子。
1个回答

2

您需要对其进行性能剖析,以确定瓶颈所在。

也许它在内核中,也许您已经达到了硬件的极限。 在您进行性能剖析之前,您将摸索在黑暗中。

编辑:

好的,这次回答更详细一些。根据 Boost.Iostreams 文档,basic_file_source 仅是 std::filebuf 的一个包装器,而 std::filebuf 又是基于 std::streambuf 构建的。引用文档:

可复制构造和可赋值封装器,用于 std::basic_filebuf 的只读模式打开。

streambuf 提供了一个方法 pubsetbuf (可能不是最好的参考,但第一个谷歌搜索结果),您可以使用它来控制缓冲区大小。

例如:

#include <fstream>

int main()
{
  char buf[4096];
  std::ifstream f;
  f.rdbuf()->pubsetbuf(buf, 4096);
  f.open("/tmp/large_file", std::ios::binary);

  while( !f.eof() )
  {
      char rbuf[1024];
      f.read(rbuf, 1024);
  }

  return 0;
}

在我的测试中(关闭优化),使用4096字节缓冲区的性能实际上比使用16字节缓冲区的性能更差,但是你的情况可能不同 -- 这是为什么你应该始终先进行分析的好例子 :)
但正如你所说,basic_file_sink没有提供任何访问方式,因为它在其私有部分隐藏了底层的filebuf
如果您认为这是错误的,您可以:
  1. 敦促Boost开发人员公开此类功能,使用邮件列表或trac。
  2. 构建自己的filebuf包装器,它确实公开了缓冲区大小。教程中有一个章节解释了编写自定义源代码的方法,这可能是一个很好的起点。
  3. 基于任何内容编写自定义源代码,以执行您想要的所有缓存操作。
请记住,您的硬盘以及内核已经对文件读取进行了缓存和缓冲处理,我认为即使从缓存更多数据,也不会获得太多的性能提升。
最后,关于分析的话题。对于Linux,有大量强大的分析工具可用,我甚至不知道其中一半的名称,但例如有iotop,它非常简单易用。它几乎就像top,但显示与磁盘相关的指标。例如:
Total DISK READ: 31.23 M/s | Total DISK WRITE: 109.36 K/s
TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND          
19502 be/4 staffan    31.23 M/s    0.00 B/s  0.00 % 91.93 % ./apa

告诉我,我的程序超过90%的时间都花在等待IO上,也就是说它是IO密集型的。如果你需要更强大的工具,我相信谷歌可以帮助你。

还要记住,在热缓存或冷缓存上进行基准测试会极大地影响结果。


也许这是另一篇帖子的问题,但我该如何对其进行分析?我知道如何使用gprof,但它只告诉我有关CPU时间的信息,而在这里,我相当确定瓶颈是磁盘I/O。或者如果有人能告诉我如何正确设置缓冲区大小,我可以尝试并查看它是否有所帮助。 - user387250
@jwfoley:我喜欢Valgrind的callgrind分析器。就我的经验而言(也就是说,我不能保证任何东西),它报告了内核调用所花费的时间,这是我从未能让gprof做到的。例如,我使用它来分析OpenGL应用程序,并且它正确地报告了在视频驱动程序代码中所花费的时间。它非常容易使用(valgrind --tool=callgrind ./your-app)。使用KCachegrind来解释结果。唯一的问题是,在分析时,您的应用程序将运行20倍左右的速度较慢。 - Staffan
@Staffan:好的,我尝试使用callgrind + KCachegrind并对分析器印象深刻,但我仍然不知道我在寻找什么。结果看起来非常类似于gprof的结果。名为T.3577的东西具有较高的"Incl."但很低的"Self;"它的大部分时间似乎花费在std::basic_ios上。也许这是磁盘I/O吗?我仍然想得到关于如何设置缓冲区大小的答案。如果很容易,那么我可以尝试一下看看是否有所帮助,但无论如何了解这个问题都会很有用。 - user387250
@Staffan:非常感谢您详细的回答。不幸的是,最重要的部分是:“请记住,硬盘和内核在文件读取时已经执行了缓存和缓冲操作。”如果我不重新编译我的内核并启用 CONFIG_TASK_DELAY_ACCT=y ,我就不能使用 iotop。但是,从我的程序运行时内存被缓存填满的事实来看,似乎很合理地假设自定义自己的缓存没有任何好处。也许那一部分已经达到了它的最快速度。无论如何,我学到了一些东西,希望我可以点赞以便其他人受益。 - user387250
@jwfoley:哦,iotop在我的Fedora机器上可以直接运行。不过,如果你需要重新编译内核,使用起来可能就不那么简单了 :) 如果答案对您有帮助,请接受答案,这是良好的Stackoverflow礼仪。 - Staffan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接