C++标准库做了哪些额外的工作?它是否符合标准?在实践中是否有用?或者一些编译器是否提供了与手动缓冲区管理相竞争的iostreams实现?
基准测试
为了推进事情的发展,我编写了几个简短的程序来测试iostreams内部缓冲:
- 将二进制数据放入
ostringstream
中 http://ideone.com/2PPYw - 将二进制数据放入
char[]
缓冲区中 http://ideone.com/Ni5ct - 使用
back_inserter
将二进制数据放入vector<char>
中 http://ideone.com/Mj2Fi - 新加入:
vector<char>
简单迭代器 http://ideone.com/9iitv - 新加入: 直接将二进制数据放入
stringbuf
中 http://ideone.com/qc9QA - 新加入:
vector<char>
简单迭代器加边界检查 http://ideone.com/YyrKy
请注意,ostringstream
和 stringbuf
版本运行的迭代次数更少,因为它们速度更慢。
ostringstream
: 53毫秒stringbuf
: 27毫秒vector<char>
和back_inserter
: 17.6毫秒vector<char>
使用普通迭代器: 10.6毫秒vector<char>
迭代器和边界检查: 11.4毫秒char[]
: 3.7毫秒
在我的笔记本电脑上 (Visual C++ 2010 x86, cl /Ox /EHsc
, Windows 7 Ultimate 64-bit, Intel Core i7, 8 GB RAM):
ostringstream
: 73.4毫秒,71.6毫秒stringbuf
: 21.7毫秒,21.3毫秒vector<char>
和back_inserter
: 34.6毫秒,34.4毫秒vector<char>
使用普通迭代器: 1.10毫秒,1.04毫秒vector<char>
迭代器和边界检查: 1.11毫秒,0.87毫秒,1.12毫秒,0.89毫秒,1.02毫秒,1.14毫秒char[]
: 1.48毫秒,1.57毫秒
使用Profile-Guided Optimization的Visual C++ 2010 x86编译器:cl /Ox /EHsc /GL /c
,link /ltcg:pgi
编译链接,运行,link /ltcg:pgo
测量:
ostringstream
:61.2毫秒,60.5毫秒- 使用普通迭代器的
vector<char>
:1.04毫秒,1.03毫秒
同样的笔记本电脑,同样的操作系统,使用cygwin gcc 4.3.4 g++ -O3
:
ostringstream
:62.7毫秒,60.5毫秒stringbuf
:44.4毫秒,44.5毫秒vector<char>
和back_inserter
:13.5毫秒,13.6毫秒- 使用普通迭代器的
vector<char>
:4.1毫秒,3.9毫秒 vector<char>
迭代器和边界检查:4.0毫秒,4.0毫秒char[]
:3.57毫秒,3.75毫秒
同样的笔记本电脑,使用Visual C++ 2008 SP1编译器:cl /Ox /EHsc
:
ostringstream
: 88.7毫秒,87.6毫秒stringbuf
: 23.3毫秒,23.4毫秒vector<char>
和back_inserter
:26.1毫秒,24.5毫秒vector<char>
使用普通迭代器:3.13毫秒,2.48毫秒vector<char>
迭代器和边界检查:2.97毫秒,2.53毫秒char[]
: 1.52毫秒,1.25毫秒
同一台笔记本电脑,使用Visual C++ 2010 64位编译器:
ostringstream
: 48.6毫秒,45.0毫秒stringbuf
: 16.2毫秒,16.0毫秒vector<char>
和back_inserter
:26.3毫秒,26.5毫秒vector<char>
使用普通迭代器:0.87毫秒,0.89毫秒vector<char>
迭代器和边界检查:0.99毫秒,0.99毫秒char[]
: 1.25毫秒,1.24毫秒
编辑:运行两次以查看结果的一致性。在我看来,结果非常一致。
注意:在我的笔记本电脑上,由于我可以拨出比ideone更多的CPU时间,所以我将所有方法的迭代次数设置为1000。这意味着只在第一次通过时发生的ostringstream
和vector
重新分配对最终结果应该没有太大影响。
编辑:糟糕,发现了一个vector
-with-ordinary-iterator中的错误,迭代器没有被提前,因此缓存命中过多。我想知道为什么vector<char>
的表现优于char[]
。不过这并没有太大的区别,在VC++ 2010下vector<char>
仍然比char[]
快。
结论
每次附加数据时,输出流的缓冲需要三个步骤:
- 检查传入块是否适合可用缓冲区空间。
- 复制传入块。
- 更新数据结束指针。
我发布的最新代码片段“vector<char>
simple iterator plus bounds check”不仅实现了这一点,还在传入块不适合时分配了额外的空间并移动了现有数据。正如Clifford指出的那样,在文件I/O类中进行缓冲不必这样做,它只需刷新当前缓冲区并重用它。因此,这应该是输出缓冲的成本上限。这正是制作工作中内存缓冲所需要的。
那么为什么在ideone上stringbuf
要慢2.5倍,在我测试时至少要慢10倍呢?在这个简单的微基准测试中,它没有被多态使用,所以这并不能解释它。
std::ostringstream
没有像std::vector
那样智能地指数级增加其缓冲区大小,那就是(A)愚蠢的,并且(B)需要考虑I/O性能的人们应该考虑这个问题。无论如何,缓冲区被重复使用,而不是每次都重新分配。std::vector
也使用动态增长的缓冲区。我在这里试图公正。 - Ben Voigtostringstream
的任何格式设置功能,并且希望尽可能快地执行,则应考虑直接使用stringbuf
。ostream
类应该通过rdbuf()
及其虚函数接口将区域设置感知格式设置功能与灵活的缓冲区选择(文件、字符串等)联系在一起。如果您没有进行任何格式化,则该额外的间接层肯定会相对于其他方法显得代价高昂。 - CB Baileyofstream
转为fprintf
,获得了数量级的速度提升。在WinXPsp3上使用MSVC 2008。iostreams非常慢。 - KitsuneYMG