【C++实验】cout和printf的速度对比
注:原来投的洛谷,但是投了 ∞ \infty ∞ 次也没过,原链接
输出,是一项很重要的事。一切信息学比赛以输出为基础,但高等级的 OI 输出太大,一不留神就 T L E \textcolor{#0000A1}{TLE} TLE。因此,我们要学习如何更快输出,毕竟没人愿意比赛因为输出过大而超时。
实验起因
洛谷有一种很少见的评测状态,叫 O L E \textcolor{#0000A1}{OLE} OLE。意思是输出超限。
举个 OLE 代码的栗子:
#include<bits/stdc++.h>usingnamespace std;intmain(){while(1){printf("555555555555555\n");}return0;}毫无疑问,第一眼看到这段代码,会让人觉得会 TLE。但事实是,printf 太快,达不到 TLE 的等级,会在规定时间内输出很多东西,于是程序会被 OJ 强行终止并判定 OLE。
那么,为什么刚刚要用 printf 创造 OLE 呢?
我一开始信了 OI 界的一个广泛的说法: cout 效率比 printf 低。其实 cout 也 OLE 了
但这只是传言,不一定是对的(对的,后面,我推翻了它)。
于是,我开始了一次实验。
实验设计
为了公平比较,保证所有测试均遵循以下条件:
- 编译环境:使用 mingw64 的 g++ 编译 使用编译参数
-std=c++14 -O2进行编译,这是竞赛的标准配置。 - 计时方法:使用 C++11 的
chrono库进行毫秒级计时,单组实验多次取平均值以减少误差。 - 测试场景:涵盖标准输出、输出重定向到文件、直接使用文件流(
ofstream) 三种典型情况。 - 测试数据:从少量( 100 100 100 个)到大量( 1 000 000 1 \ 000 \ 000 1 000 000 个)递增,以观察不同数据规模下的表现。
实验过程
标准输入输出
标准输入输出流版本:
#include<iostream>#include<cstdio>#include<chrono>usingnamespace std;intmain(){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;return0;}输出:
11111111111111111111111111111······(后略) time:15ms cout << 1 换成 printf("1"); ,平均时间直接只剩10毫秒。
多次数据证明:此时printf 较快。
文件读写
我们先看看文件重定向 +printf 的速度。
#include<iostream>#include<cstdio>#include<chrono>usingnamespace std;intmain(){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);return0;}输出:0ms。
还有和标准流相对的文件流,也是相当著名的文件输入输出方法。
#include<fstream>#include<chrono>usingnamespace std;intmain(){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);return0;}输出:0ms。
这些数据证明小规模文件操作和 std 相比还是很快的。
加大数据量
换成输出 100000 100000 100000 个 1 1 1:
#include<fstream>#include<chrono>usingnamespace std;intmain(){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);return0;}输出:
1111111111111111······(后略) time:1ms #include<iostream>#include<cstdio>#include<chrono>usingnamespace std;intmain(){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);return0;}输出:
1111111111111111······(后略) time:6ms 时间依然很少,代表测试规模依然不够。
为了真正显现出两者速度的差别,我们加大数据,让它输出 10000000 10000000 10000000 个 1 1 1。
11111111111111111111······(后略) time:56ms 这一次,即使是 printf 也花了 56 56 56 毫秒。
测测 cout,结果竟是 27 27 27 毫秒。
测了 10 次,最慢就是 27 毫秒。因此这个数据正式破除了一个言论:cout 永远比 printf 慢。事实证明,它在文件读写更胜一筹。
这次使用的是文件重定向,而文件流(fout)的表现则更让人惊讶:仅仅 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不是能一下能输出完的,测不了。
局部由 Deepseek 优化。
之所以把 fout 拿来测,是因为觉得 fout 是 cout 在文件读写方面的优化。