cout和printf的速度实验
Github_564598 · · 休闲·娱乐
注:1.11改为休闲·娱乐,因为投不进去。
输出,是一项很重要的事。一切比赛以输出为基础,但高等级的 OI 输出太大,一不留神就
实验起因
洛谷有一种很少见的评测状态,叫
#include <bits/stdc++.h>
using namespace std;
int main()
{
while (1)
{
printf("555555555555555\n");
}
return 0;
}
毫无疑问,第一眼看到这段代码,会让人觉得会 TLE。但事实是,printf 太快,达不到 TLE 的等级,会在规定时间内输出很多东西,于是程序会被 OJ 强行终止并判定 OLE。
那么,为什么刚刚要用 printf 创造 OLE 呢?
我一开始信了 OI 界的一个广泛的说法: cout 效率比 printf 低。
但这只是传言,不一定是对的(没错,后面,我推翻了它)。 于是,我开始了一次实验。
实验设计
为了公平比较,保证所有测试均遵循以下条件:
- 编译环境:使用 mingw64 的 g++ 编译 使用编译参数
-std=c++14 -O2进行编译,这是竞赛的标准配置。 - 计时方法:使用 C++11 的
chrono库进行毫秒级计时,循环多次取平均值以减少误差。 - 测试场景:涵盖标准输出、输出重定向到文件、直接使用文件流(
ofstream) 三种典型情况。 - 测试数据:从少量(100 个)到大量(1,000,000 个)递增,以观察不同数据规模下的表现
实验过程
标准输入输出
标准输入输出流版本:
#include <iostream>
#include <cstdio>
#include <chrono>
using namespace std;
int main() {
auto program_start = std::chrono::high_resolution_clock::now();
for ( int i = 0 ; i < 100 ; i ++ )
{
cout << 1;
}
auto program_end = std::chrono::high_resolution_clock::now();
auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(program_end - program_start);
cout << "time:" << total_duration.count() << "ms" << endl;
return 0;
}
输出:
11111111111111111111111111111······(后略)
time:15ms
cout << 1 换成 printf("1"); ,平均时间直接只剩10毫秒。
多次数据证明:此时printf 较快。
文件读写
我们先看看文件重定向 +printf 的速度。
#include <iostream>
#include <cstdio>
#include <chrono>
using namespace std;
int main() {
auto program_start = std::chrono::high_resolution_clock::now();
freopen("test.out" , "w" , stdout);
for ( int i = 0 ; i < 100 ; i ++ )
{
printf("1");
}
auto program_end = std::chrono::high_resolution_clock::now();
auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(program_end - program_start);
cout << "time:" << total_duration.count() << "ms" << std::endl;
fclose(stdout);
return 0;
}
输出:0ms。
还有和标准流相对的文件流,也是相当著名的文件输入输出方法。
#include <fstream>
#include <chrono>
using namespace std;
int main() {
auto program_start = std::chrono::high_resolution_clock::now();
ofstream fout("test.out");
for ( int i = 0 ; i < 100 ; i ++ )
{
fout << 1;
}
auto program_end = std::chrono::high_resolution_clock::now();
auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(program_end - program_start);
fout << "time:" << total_duration.count() << "ms" << std::endl;
fclose(stdout);
return 0;
}
输出:0ms。
这些数据证明小规模文件操作和 std 相比还是很快的。
加大数据量
换成输出
#include <fstream>
#include <chrono>
using namespace std;
int main() {
auto program_start = std::chrono::high_resolution_clock::now();
ofstream fout("test.out");
for ( int i = 0 ; i < 100000 ; i ++ )
{
fout << 1;
}
auto program_end = std::chrono::high_resolution_clock::now();
auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(program_end - program_start);
fout << endl<< "time:" << total_duration.count() << "ms" << endl;
fclose(stdout);
return 0;
}
输出:
1111111111111111······(后略)
time:1ms
#include <iostream>
#include <cstdio>
#include <chrono>
using namespace std;
int main() {
auto program_start = std::chrono::high_resolution_clock::now();
freopen("test.out" , "w" , stdout);
for ( int i = 0 ; i < 100000 ; i ++ )
{
printf("1");
}
auto program_end = std::chrono::high_resolution_clock::now();
auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(program_end - program_start);
cout << endl << "time:" << total_duration.count() << "ms" << std::endl;
fclose(stdout);
return 0;
}
输出:
1111111111111111······(后略)
time:6ms
时间依然很少,代表测试规模依然不够。
为了真正显现出两者速度的差别,我们加大数据,让它输出
11111111111111111111······(后略)
time:56ms
这一次,即使是 printf 也花了 56 毫秒。
测测 cout,结果竟是27 毫秒。
测了 10 次,最慢就是 27 毫秒。因此这个数据正式破除了一个言论:cout 永远比 printf 慢。事实证明,它在文件读写更胜一筹。
这次使用的是文件重定向,而文件流的表现则更让人惊讶:仅仅 16 毫秒。
实验结论
数据分析:
- 在小数据量情况下,
cout确实慢于printf,cout由于需要维护类型安全并可能与 C 标准流同步,其单次函数调用的开销略高于printf。 - 在文件读写且中小数据量时,两者相差不多,因为重定向和文件流的缓冲区都够大,磁盘 IO 会在关闭文件时统一处理。
- 在大规模文件读写时,
cout和fout更大缓冲区的好处就出来了:真实磁盘 IO 次数少很多。因此它们相对缓冲区小且更保守的printf快。 cout的输出在文件 IO 时还得重定向,fout根本不用,缓冲区策略也激进,磁盘 IO 更少,所以在所有文件测试中都领先。
| 具体数据如下表: | 输出 1 的数量 | 文件读写形式 | 使用语句 | 时间(取平均值) |
|---|---|---|---|---|
| 100 个 | / | cout<<1; |
15ms | |
| 100 个 | / | printf("1"); |
10ms | |
| 100 个 | 文件重定向 | printf("1"); |
0ms | |
| 100 个 | 文件重定向 | cout << 1; |
0ms | |
| 100 个 | 文件流 | fout << 1; |
0ms | |
| 100000 个 | 文件重定向 | printf("1") |
6ms | |
| 100000 个 | 文件重定向 | fout << 1; |
2ms | |
| 100000 个 | 文件流 | fout << 1; |
1ms | |
| 1000000 个 | 文件重定向 | printf("1"); |
56ms | |
| 1000000 个 | 文件重定向 | fout << 1; |
26ms | |
| 1000000 个 | 文件流 | fout << 1; |
16ms |
从以上数据可得出,对于标准输入输出,printf 更快;对于文件读写,经过优化的 cout 更胜一筹。
总之,对于常规 OJ 刷题,推荐 cout(求速度使用 ios::sync_with_stdio(false);,会关闭与C标准流的同步);对于需格式化输出的时候,iomanip内的工具开销较大,所以推荐printf;正规比赛时,还得用 fout 等,重定向顺手但相对其较慢,而且有些比赛不让用。
注意:使用 cout 时少用 std::endl ,会自动清空缓冲区,多一次 IO。
注释/后记
之所以加大数据量中没测标准输入输出,是因为百万个 1不是能一下能输出完的,测不了。