【问题标题】:How to change buffer size with boost::iostreams?如何使用 boost::iostreams 更改缓冲区大小?
【发布时间】:2010-07-08 23:47:49
【问题描述】:

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

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

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

根据documentationfile_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?如果是后者,它是如何工作的?文档中的示例并不完整。

【问题讨论】:

    标签: c++ boost


    【解决方案1】:

    您也应该对其进行分析,看看瓶颈在哪里。

    也许它在内核中,也许是你的硬件限制。直到您对其进行分析以发现您在黑暗中跌跌撞撞。

    编辑:

    好的,那么这次来个更彻底的答案。根据 Boost.Iostreams 文档,basic_file_source 只是std::filebuf 的一个包装器,而它又是基于std::streambuf 构建的。引用文档:

    以只读模式打开的 std::basic_filebuf 的 CopyConstructible 和 Assignable 包装器。

    streambuf 确实提供了一个方法 pubsetbuf(也许不是最好的参考,但第一个 google 出现了),显然您可以使用它来控制缓冲区大小。

    例如:

    #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 字节缓冲区的性能更差,但是 YMMV - 一个很好的例子,说明为什么你应该总是首先分析 :)

    但是,正如您所说,basic_file_sink 不提供任何访问方法,因为它在其private part 中隐藏了底层的filebuf

    如果您认为这是错误的,您可以:

    1. 敦促 Boost 开发人员公开此类功能,使用邮件列表或 trac。
    2. 构建您自己的filebuf 包装器,它会公开缓冲区大小。教程中有一个section,它解释了编写自定义源代码,这可能是一个很好的起点。
    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 绑定的。如果您需要更强大的功能,我相信 google 可以帮助您。

    请记住,对热缓存或冷缓存进行基准测试会极大地影响结果。

    【讨论】:

    • 也许这是另一个帖子的问题,但我将如何分析它?我知道如何使用 gprof,但它只告诉我 CPU 时间,而且我很确定瓶颈是磁盘 I/O。或者如果有人能告诉我如何正确设置缓冲区大小,我可以试试看是否有帮助。
    • @jwfoley:我喜欢Valgrind's callgrind profiler。就我的经验而言(读作:我不能保证任何事情)它还报告了内核调用所花费的时间,这是我永远无法让 gprof 做的事情。例如,我用它来分析一个带有 OpenGL 的应用程序,它正确地报告了在视频驱动程序代码中花费的时间。它非常易于使用(valgrind --tool=callgrind ./your-app)。使用KCachegrind 解释结果。唯一的问题是您的应用程序在分析时运行速度会慢 20 倍左右。
    • @Staffan:好的,我尝试了 callgrind + KCachegrind,我对分析器印象深刻,但我仍然不知道我在寻找什么。结果看起来与 gprof 非常相似。称为 T.3577 的东西具有很高的“包含”。但低“自我”;它的大部分时间似乎都花在了 std::basic_ios 上。也许这就是磁盘 I/O?我仍然想回答我最初关于如何设置缓冲区大小的问题。如果这很容易,那么我可以尝试一下,看看它是否有帮助,但无论如何知道它会很有用。
    • @Staffan:感谢您非常彻底的回答。不幸的是,最重要的部分是:“请记住,您的硬盘驱动器以及内核已经在文件读取时进行缓存和缓冲”。除非我用 CONFIG_TASK_DELAY_ACCT=y 重新编译我的内核,否则我不能使用 iotop,但是从我的内存在我运行我的程序时被缓存填满的事实来看,假设通过自定义我自己的缓存没有任何好处似乎是很合理的。也许这部分的速度会很快。无论如何,我学到了一些东西,我希望我能对此表示赞同,以便其他人受益。
    • @jwfoley:哦,iotop 在我的 Fedora 机器上开箱即用。不过,我想如果您必须重新编译内核,使用起来并不是那么简单:) 如果答案令您满意,那么接受答案是很好的 Stackoverflow 礼节。
    猜你喜欢
    • 2015-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多