C++技巧:cin/cout和scanf/printf的速度
文章目录
如果不做特殊处理,cin/cout的速度比scanf/printf慢得多,做算法时经常Time Limit Exceeded。 但使用一些技巧,cin/cout的速度可以达到甚至超过scanf/printf
测试程序
产生大量随机数并写入文件
|
|
使用scanf/printf
|
|
|
|
使用cin/cout
|
|
|
|
只是使用cin/cout代替了前面的scanf/printf,但scanf/printf只需数秒,而cin/cout需要数分钟
endl的影响
在[2]中有说<<endl
等价于<<"\n"<<flush
,即每次使用endl都会刷新缓冲区,IO开销很大。
|
|
|
|
该版本使用"\n"
代替endl
,但运行时间和使用endl
是一致的。
如果不写入文件./out而是直接打印到终端,还会发现仍是在不断刷缓冲,并没有等到缓冲区满再输出。
tie的影响
造成上面用"\n"
代替endl
运行时间仍一致的原因是cin被关联(tie)
到cout上(原因如[2]所说,与用户交互的程序需保证让用户输入前,已经打印完了需要打印的内容)。
当读写被关联的流时,关联到的流的缓冲被刷新。即,每次读取cin时,cout都被刷新。因此仍是每个循环刷新一次缓冲。
异步读写
一种解决方案是将cin读到的数据存起来,将cin和cout分离
|
|
|
|
该版本将cin和cout分离到两个循环中,异步读写。 即使输入时增加了向vector添加的开销,输出时增加了从vector中取元素的开销, 速度仍大幅提高,达到秒级。
解除关联
更好的解决方案是将cin和cout解除关联,方法是将cin关联到另一个空的流上,即使用cin.tie(nullptr)
|
|
|
|
解除关联后,即使同步输入/输出,时间也在秒级,但仍比scanf/printf慢
与C流同步的影响
在[5]和[6]中介绍了同时使用scanf/printf和cin/cout时,是在同时使用C stream和C++ stream两种流。 在[6]中提到:
Sets whether the standard C++ streams are synchronized to the standard C streams after each input/output operation.
The standard C++ streams are the following:std::cin
,std::cout
,std::cerr
,std::clog
,std::wcin
,std::wcout
,std::wcerr
andstd::wclog
The standard C streams are the following:stdin
,stdout
andstderr
In practice, this means that the synchronized C++ streams are unbuffered, and each I/O operation on a C++ stream is immediately applied to the corresponding C stream’s buffer. This makes it possible to freely mix C++ and C I/O.
在混合使用scanf/printf和cin/cout时,为了保证同步,即保证[5]中的示例代码能以正确的顺序运行,每个C++ stream都会同步到一个C stream上,保证这个同步需要一些开销。
有时候为保证stream的同步,花一点开销是值得的,例如[6]中介绍的保证线程安全
的情形。但不需要同步的时候将std::ios_base::sync_with_stdio
设为false可减少这些开销。
|
|
|
|
解除C stream和C++ stream的同步后,时间进一步缩短,使用cin/cout的时间少于使用scanf/printf
根据[1]中描述,在数据量增大时cin/cout比scanf/printf更快的原因之一是: cin/cout是模板类,可以在编译期确定类型。而scanf/printf是普通函数,只能在运行期确定类型
总结
- 不需要即时打印时,使用
<<"\n"
代替<<endl
,后者会刷新缓冲,产生IO开销 - 不需要与用户交互时,使用
cin.tie(nullptr)
将cin和cout解除关联,避免每次读cin都引起cout刷新 - 不需要保证流同步时,使用
ios_base::sync_with_stdio(false)
解除C++ stream和C stream的同步,进一步减少开销
其他材料
[1]中比较全面地说了本文的三个要点。
[2]是C++ primer某一节的笔记,讲了缓冲区刷新、endl、tie。
[3]是tie的手册。
[4]是stackoverflow的一个问题,下面回答提到了sync_with_stdio并给出示例。
[5]是libstdc++的手册,讲了同时使用scanf/printf和cin/cout的同步。
[6]是sync_with_stdio的手册,讲了C++ stream和C stream的同步。
Reference
[1]:C++的輸出入cin/cout和scanf/printf誰比較快?
[2]:我的博客:C++ primer 8.1.3 #管理输出缓冲
[3]:cppreference.com - std::basic_ios<CharT,Traits>::tie
[4]:Using scanf() in C++ programs is faster than using cin?
[5]:GNU libstdc++ manual - Chapter 13. Input and Output - Interacting with C
[6]:cppreference.com - std::ios_base::sync_with_stdio