Ben*_*igt 193 c++ performance iostream
每当我提到C++标准库iostream的慢性能时,我都会遇到一阵难以置信的风潮.然而,我有剖析器结果显示在iostream库代码中花费了大量时间(完全编译器优化),并且从iostream切换到特定于操作系统的I/O API和自定义缓冲区管理确实提供了一个数量级的改进.
C++标准库做了多少额外工作,标准是否需要它,它在实践中是否有用?或者有些编译器提供了与手动缓冲区管理竞争的iostream实现吗?
为了解决问题,我编写了几个简短的程序来练习iostreams内部缓冲:
ostringstream http://ideone.com/2PPYwchar[]缓冲区http://ideone.com/Ni5ctvector<char>使用http://ideone.com/Mj2Fi将二进制数据放入其中back_inserter vector<char>简单的迭代器http://ideone.com/9iitvstringbuf http://ideone.com/qc9QAvector<char>简单的迭代器加边界检查http://ideone.com/YyrKy请注意,ostringstream和stringbuf版本运行的迭代次数较少,因为它们的速度要慢得多.
在ideone上,它ostringstream比std:copy+ back_inserter+ 慢大约3倍std::vector,比memcpy原始缓冲区慢大约15倍.当我将实际应用程序切换到自定义缓冲时,这与前后分析一致.
这些都是内存缓冲区,因此iostream的缓慢不能归咎于缓慢的磁盘I/O,过多的刷新,与stdio的同步,或者人们用来解释C++标准库观察到的缓慢的任何其他事情iostream的.
很高兴看到其他系统上的基准测试和常见实现的评论(例如gcc的libc ++,Visual C++,Intel C++)以及标准规定了多少开销.
许多人都正确地指出,iostream更常用于格式化输出.但是,它们也是C++标准提供的二进制文件访问的唯一现代API.但是对内部缓冲进行性能测试的真正原因适用于典型的格式化I/O:如果iostreams无法保持磁盘控制器提供原始数据,那么当他们负责格式化时,他们怎么可能跟上呢?
所有这些都是outer(k)循环的每次迭代.
在ideone上(gcc-4.3.4,未知的操作系统和硬件):
ostringstream:53毫秒stringbuf:27毫秒vector<char>并且back_inserter:17.6毫秒vector<char> 与普通迭代器:10.6毫秒vector<char> 迭代器和边界检查:11.4 mschar[]:3.7毫秒在我的笔记本电脑上(Visual C++ 2010 x86,cl /Ox /EHscWindows 7旗舰版64位,Intel Core i7,8 GB RAM):
ostringstream:73.4毫秒,71.6毫秒stringbuf:21.7 ms,21.3 msvector<char>和back_inserter:34.6毫秒,34.4毫秒vector<char> 与普通迭代器:1.10毫秒,1.04毫秒vector<char> 迭代器和边界检查:1.11 ms,0.87 ms,1.12 ms,0.89 ms,1.02 ms,1.14 mschar[]:1.48毫秒,1.57毫秒VISUAL C++ 2010 x86上,与档案导引优化cl /Ox /EHsc /GL /c,link /ltcg:pgi运行,link /ltcg:pgo,措施:
ostringstream:61.2 ms,60.5 msvector<char> 与普通迭代器:1.04毫秒,1.03毫秒相同的笔记本电脑,相同的操作系统,使用cygwin gcc 4.3.4 g++ -O3:
ostringstream:62.7 ms,60.5 msstringbuf:44.4毫秒,44.5毫秒vector<char>和back_inserter:13.5毫秒,13.6毫秒vector<char> 使用普通迭代器:4.1 ms,3.9 msvector<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 ms,23.4 msvector<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 ms,16.0 msvector<char>和back_inserter:26.3毫秒,26.5毫秒vector<char> 普通迭代器:0.87 ms,0.89 msvector<char> 迭代器和边界检查:0.99毫秒,0.99毫秒char[]:1.25毫秒,1.24毫秒编辑:全部跑两次,看看结果是多么一致.相当一致的IMO.
注意:在我的笔记本电脑上,因为我可以节省比ideone允许更多的CPU时间,所以我将所有方法的迭代次数设置为1000.这意味着,ostringstream和vector重新分配,这仅发生在第一轮,应该对最终结果的影响很小.
编辑:糟糕,在vector-with-ordinary-iterator中发现了一个错误,迭代器没有被提升,因此缓存命中次数过多.我想知道如何vector<char>表现出色char[].虽然它没有太大的区别,但vector<char>仍然比char[]VC++ 2010 更快.
每次附加数据时,输出流的缓冲需要三个步骤:
我发布的最新代码片段" vector<char>simple iterator plus bounds check"不仅可以实现这一点,还可以分配额外的空间,并在传入块不适合时移动现有数据.正如Clifford指出的那样,在文件I/O类中缓冲不必这样做,它只是刷新当前缓冲区并重用它.所以这应该是缓冲输出成本的上限.它正是制作工作内存缓冲区所需要的.
那么为什么stringbuf在ideone上放慢2.5倍,在测试时放慢至少10倍?它并没有在这个简单的微基准测试中使用多态,因此不能解释它.
bel*_*daz 47
没有像标题那样回答你的问题的具体细节:2006年关于C++性能的技术报告有一个关于IOStreams的有趣部分(p.68).与您的问题最相关的是第6.1.2节("执行速度"):
由于IOStreams处理的某些方面分布在多个方面,因此标准似乎要求实施效率低下.但事实并非如此 - 通过使用某种形式的预处理,可以避免大部分工作.使用比通常使用的更聪明的链接器,可以消除这些低效率中的一些.这将在§6.2.3和§6.2.5中讨论.
由于该报告是在2006年编写的,人们希望许多建议能够被纳入当前的编制者,但也许情况并非如此.
如你所述,facets可能没有特色write()(但我不会盲目地假设).那么功能是什么?在ostringstream使用GCC编译的代码上运行GProf 会产生以下细分:
std::basic_streambuf<char>::xsputn(char const*, int)std::ostream::write(char const*, int)mainstd::ostream::sentry::sentry(std::ostream&)std::string::_M_replace_safe(unsigned int, unsigned int, char const*, unsigned int)std::basic_ostringstream<char>::basic_ostringstream(std::_Ios_Openmode)std::fpos<int>::fpos(long long)所以大部分时间花在了xsputn,最终std::copy()在大量检查和更新光标位置和缓冲区之后调用(查看c++\bits\streambuf.tcc详细信息).
我对此的看法是,你专注于最坏情况.如果您处理相当大的数据块,所执行的所有检查将只是完成的总工作的一小部分.但是您的代码一次只能以四个字节的速度移动数据,并且每次都会产生所有额外成本.很明显,人们会避免在现实生活中这样做 - 考虑如果write在一个1m的数组上调用而不是在1m的1m上调用,那么惩罚是多么微不足道.在现实生活中,人们会非常欣赏IOStream的重要特性,即其内存安全和类型安全设计.这样的好处是有代价的,你编写了一个测试,使这些成本占据执行时间.
Ben*_*igt 27
我对那里的Visual Studio用户感到非常失望,他们更喜欢这个用户:
ostream,sentry对象(标准所需)进入保护streambuf(不需要)的关键部分.这似乎不是可选的,因此即使对于单个线程使用的本地流(无需同步),您也要支付线程同步的成本.这会伤害ostringstream用于格式化消息的代码.使用stringbuf直接避免使用sentry,但格式化插入操作符不能直接在streambufs 上工作.对于Visual C++ 2010,关键部分ostringstream::write与底层stringbuf::sputn调用相比减慢了三倍.
看看beldaz在newlib上的探查器数据,很明显gcc sentry没有像这样做任何疯狂的事情. ostringstream::write在gcc下只需要大约50%的时间stringbuf::sputn,但stringbuf本身比VC++慢得多.并且两者仍然非常不利地使用vector<char>for I/O缓冲,尽管与VC++不同.
您看到的问题是每次调用write()时的开销.你添加的每个抽象级别(char [] - > vector - > string - > ostringstream)都会增加一些函数调用/返回和其他内务处理guff - 如果你称之为一百万次 - 加起来.
我修改了ideone上的两个例子,一次写入十个整数.ostringstream时间从53到6毫秒(几乎10倍的改进),而char循环改进(3.7到1.5) - 很有用,但只有两倍.
如果你担心性能,那么你需要为工作选择合适的工具.ostringstream是有用且灵活的,但是按照你想要的方式使用它会受到惩罚.char []是更难的工作,但性能提升可能很大(记住gcc可能也会为你编写memcpys).
简而言之,ostringstream没有被破坏,但是越接近金属,代码运行得越快.汇编程序对某些人来说仍然有优势.