为什么ostringstream比ofstream更快?

11
为了将许多数据写入文件,我有两种方法:
  1. 直接逐个将数据写入 ofstream

    ofstream file("c:\\test.txt");
    for (int i = 0; i < 10000; ++i)
    {
        file << data[i];
    }
    
  2. 先将数据写入istringstream,然后一次性将其写入ofstream

  3. ostringstream strstream;
    for (int i = 0; i < 10000; ++i)
    {
        strstream << data[i];
    }
    ofstream file("c:\\test.txt");
    file << strstream.str();
    
    不意外地,第二种方法更快,事实上,在我的HP7800机器上,它比第一种方法快4倍。
    但是为什么呢?我知道ofstream在内部使用filebuf,而ostringstream使用stringbuf-作为缓冲区,它们都应该驻留在内存中,因此不应该有任何区别。
    底层有什么区别?
3个回答

20
你是否频繁使用 std::endl 而不是 '\n'std::endl 实际上执行了两个操作,首先将一个 '\n' 插入流中,然后刷新缓冲区到磁盘。我曾经看到这样的代码性能严重下降。(修复后代码运行速度提高了5-10倍)
将缓冲区刷新到字符串缓冲区会比刷新到磁盘快得多,因此这可能解释了你的发现。
如果并非如此,请考虑增加缓冲区的大小:
const std::size_t buf_size = 32768;
char my_buffer[buf_size];
ofstream file("c:\\test.txt");
file.rdbuf()->pubsetbuf(my_buffer, buf_size);

for (int i = 0; i < 10000; ++i)
{
    file << data[i];
}

1
谢谢,这是由于频繁使用 std::endl 引起的刷新。而且事实证明,在内存缓冲区不足时,filebuf 和 stringbuf 都可以增加其内存缓冲区大小。详见这里 - Baiyan Huang
现在我正在思考为什么它提供了一个 pubsetbuf 来设置缓冲区。也许是用在我们已经有一些内容存储在内存中,而不是将其附加到 streambuf 上时,我们只需让 streambuf 指向它。 - Baiyan Huang
2
@lzprgmr:1)所以我的第一个假设是正确的。很好。2)流缓冲区通常不会增加它们的缓冲区大小,而是在溢出时刷新到设备上。字符串流缓冲区是一个例外,因为它的“设备”是一个内存缓冲区(从中可以获取字符串)。3)当您更了解环境/设备时,可以使用pubsetbuf()。然后,您可以为流提供任何您认为合适的大小的缓冲区。我不确定是否存在除提供自定义大小的缓冲区之外的用例。 - sbi
我进行了一项测试,如果缓冲区的大小不足,filebuf 将被强制刷新 - 不像 stringbuf 一样继续增长。 - Baiyan Huang
@lzprgmr:我相信这就是我写的内容。 - sbi
1
哇,由于将 endl 切换为 "\n",速度大幅提升,感谢! - Tim Johnsen

5
磁盘速度较慢。许多小的写操作比一个大的写操作更加耗费资源。

但是这应该通过缓冲来避免。 - sbi
1
ofstream不会无限制地缓冲,它将在内部缓冲区达到一个阈值时进行写操作。 - Erik
你知道 threshold 是什么吗?有任何参考资料吗?从 这个 API 描述来看,如果内存不够,它似乎会增加内存。 - Baiyan Huang
@lzprgmr:抱歉,我不知道,我相信这取决于实现。sbi的答案展示了如何更改它。 - Erik

2
可能是特定操作系统的实现问题。我猜想 ofstream 缓冲区长度 (buflen) 小于 10000,一个典型值为 4095。因此,请尝试使用 i<4096 运行,响应时间应该相同!
第二种情况下更快的原因在于:
在第一种情况下,当缓冲区已满 (buflen=4095bytes) 时,数据将被写入磁盘。因此,在 i<10000 的情况下,它将被刷新 3 次。
而在第二种情况下,所有数据首先准备在 RAM 中,并一次性刷新到硬盘中。因此,节省了两次刷新!

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