std::fstream 缓冲与手动缓冲(为啥手动缓冲有 10 倍增益)?

Posted

技术标签:

【中文标题】std::fstream 缓冲与手动缓冲(为啥手动缓冲有 10 倍增益)?【英文标题】:std::fstream buffering vs manual buffering (why 10x gain with manual buffering)?std::fstream 缓冲与手动缓冲(为什么手动缓冲有 10 倍增益)? 【发布时间】:2012-10-11 10:02:56 【问题描述】:

我测试了两种书写配置:

    Fstream 缓冲:

    // Initialization
    const unsigned int length = 8192;
    char buffer[length];
    std::ofstream stream;
    stream.rdbuf()->pubsetbuf(buffer, length);
    stream.open("test.dat", std::ios::binary | std::ios::trunc)
    
    // To write I use :
    stream.write(reinterpret_cast<char*>(&x), sizeof(x));
    

    手动缓冲:

    // Initialization
    const unsigned int length = 8192;
    char buffer[length];
    std::ofstream stream("test.dat", std::ios::binary | std::ios::trunc);
    
    // Then I put manually the data in the buffer
    
    // To write I use :
    stream.write(buffer, length);
    

我期待同样的结果...

但是我的手动缓冲将性能提高了 10 倍以写入 100MB 的文件,并且 fstream 缓冲与正常情况相比没有任何改变(没有重新定义缓冲区)。

有人对这种情况有解释吗?

编辑: 这是新闻:刚刚在超级计算机上完成的基准测试(Linux 64 位架构,持续英特尔至强 8 核,Lustre 文件系统和......希望配置良好的编译器) (而且我没有解释 1kB 手动缓冲区“共振”的原因......)

编辑 2: 以及 1024 B 的共振(如果有人对此有想法,我很感兴趣):

【问题讨论】:

g++ 4.7.1 在 Ubuntu 12.04 上(在 Window 7 x64 pro 上的 VirtualBox 内),带有 SSD 磁盘。我将在超级计算机上进行测试,然后返回测试结果。 你能检查默认缓冲大小吗,比如这个问题:***.com/questions/10350759/… 当然开启了优化?很抱歉这个问题,但是当有人谈论衡量性能并且没有说明平台或编译方法时,这总是首先要问的问题。 我建议你检查 C++ 库的源代码,看看它的作用取决于缓冲区。 您能否发布完整的、可编译的测试程序以进行基准测试(即使是在 ideone.com 等外部网站上)? 【参考方案1】:

我想解释一下second chart中出现峰值的原因。

事实上,std::ofstream 使用的虚函数会导致性能下降,类似于我们在第一张图片中看到的情况,但它没有给出答案为什么当手动缓冲区大小较小时会达到最高性能超过 1024 字节

问题与writev()write()系统调用的高成本以及std::filebuf内部类std::ofstream的内部实现有关。

为了展示write() 对性能的影响,我在我的Linux 机器上使用dd 工具进行了一个简单的测试,以复制具有不同缓冲区大小的10MB 文件(bs 选项):

test@test$ time dd if=/dev/zero of=zero bs=256 count=40000
40000+0 records in
40000+0 records out
10240000 bytes (10 MB) copied, 2.36589 s, 4.3 MB/s

real    0m2.370s
user    0m0.000s
sys     0m0.952s
test$test: time dd if=/dev/zero of=zero bs=512 count=20000
20000+0 records in
20000+0 records out
10240000 bytes (10 MB) copied, 1.31708 s, 7.8 MB/s

real    0m1.324s
user    0m0.000s
sys     0m0.476s

test@test: time dd if=/dev/zero of=zero bs=1024 count=10000
10000+0 records in
10000+0 records out
10240000 bytes (10 MB) copied, 0.792634 s, 12.9 MB/s

real    0m0.798s
user    0m0.008s
sys     0m0.236s

test@test: time dd if=/dev/zero of=zero bs=4096 count=2500
2500+0 records in
2500+0 records out
10240000 bytes (10 MB) copied, 0.274074 s, 37.4 MB/s

real    0m0.293s
user    0m0.000s
sys     0m0.064s

如您所见:缓冲区越小,写入速度越低,因此dd 在系统空间中花费的时间越多。因此,当缓冲区大小减小时,读/写速度会降低。

但是为什么在主题创建者手动缓冲测试中手动缓冲大小小于 1024 字节时速度会达到峰值为什么它几乎是恒定的

解释与std::ofstream 实现有关,尤其是与std::basic_filebuf 相关。

默认情况下,它使用 1024 字节缓冲区(BUFSIZ 变量)。因此,当您使用小于 1024 的片段写入数据时,writev()(不是write())系统调用至少会为两个ofstream::write() 操作调用一次(片段的大小为 1023 ofstream::write() 的速度不依赖于峰值前的手动缓冲区大小(write() 至少被调用两次很少)。

当您尝试使用 ofstream::write() 调用一次写入大于或等于 1024 字节的缓冲区时,会为每个 ofstream::write 调用 writev() 系统调用。因此,您会看到当手动缓冲区大于 1024(峰值之后)时速度会增加。

此外,如果您想使用streambuf::pubsetbuf()std::ofstream 缓冲区设置为大于1024 缓冲区(例如,8192 字节缓冲区)并调用ostream::write() 以使用1024 大小的块写入数据,您会感到惊讶写入速度将与使用 1024 缓冲区相同。这是因为实现std::basic_filebuf - std::ofstream 的内部类 - 被硬编码以强制调用系统writev() 调用每个ofstream::write() 调用通过时缓冲区大于或等于 1024 字节(参见 basic_filebuf::xsputn() 源代码)。在2014-11-05 报告的 GCC bugzilla 中还有一个未解决的问题。

因此,可以使用两种可能的情况来解决这个问题:

用你自己的类替换std::filebuf并重新定义std::ofstream 将必须传递给ofstream::write()的缓冲区划分为小于1024的块,并逐个传递给ofstream::write() 不要将小块数据传递给ofstream::write(),以免降低std::ofstream 虚函数的性能

【讨论】:

【参考方案2】:

我想在现有响应中补充一点,如果写入大块数据,这种性能行为(来自虚拟方法调用/间接的所有开销)通常不是问题。问题和这些先前的答案似乎被忽略了(尽管可能隐含地理解)是原始代码每次都写入少量字节。只是为了向其他人澄清一下:如果您正在写入大块数据 (~kB+),则没有理由期望手动缓冲与使用 std::fstream 的缓冲有显着的性能差异。

【讨论】:

【参考方案3】:

这基本上是由于函数调用开销和间接性造成的。 ofstream::write() 方法继承自 ostream。该函数未内联在 libstdc++ 中,这是开销的第一个来源。然后 ostream::write() 必须调用 rdbuf()->sputn() 来进行实际的写入,这是一个虚函数调用。

最重要的是,libstdc++ 将 sputn() 重定向到另一个虚函数 xsputn(),它添加了另一个虚函数调用。

如果您自己将字符放入缓冲区,则可以避免这种开销。

【讨论】:

我不得不承认我无法理解为什么这个答案是正确的。两个版本都使用stream::write,那么为什么要不同呢? pubsetbuf then open 应该和 ifstream cstr 有同样的效果。 @AJed:问题中没有明确说明,但假设在第一种情况下,在编写x 时,这些写入很小,因此您最终会进行更多调用以这种方式发送到stream.write 以获得相同数量的数据。

以上是关于std::fstream 缓冲与手动缓冲(为啥手动缓冲有 10 倍增益)?的主要内容,如果未能解决你的问题,请参考以下文章

VAO 与 VBO 和 IBO 的手动绑定

文件相关函数

为啥 std::fstream 返回 void 而不是 bool

找不到变量:缓冲区

结构-行为-样式-Jquery实现延迟加载特效(数据缓冲特效)

为啥我的索引缓冲区没有与 glDrawArraysInstanced (OpenGLES 3.0) 一起使用