实验起因
输出是信息学竞赛的基础,但高等级 OI 中输出量过大容易导致 TLE。因此,学习如何更快输出至关重要。
关于 OLE
洛谷有一种评测状态叫 OLE(Output Limit Exceeded),意为输出超限。
示例代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
while(1){
printf("555555555555555\n");
}
return 0;
}
这段代码会在规定时间内输出大量内容,导致程序被 OJ 强行终止并判定 OLE。这引发了一个广泛说法:cout 效率比 printf 低。为了验证这一传言,我进行了一次实验。
实验设计
为保证公平比较,所有测试遵循以下条件:
- 编译环境:使用 mingw64 的 g++ 编译,参数
-std=c++14 -O2。 - 计时方法:使用 C++11 的
chrono库进行毫秒级计时,单组实验多次取平均值。 - 测试场景:涵盖标准输出、输出重定向到文件、直接使用文件流 (
ofstream) 三种情况。 - 测试数据:从少量(100 个)到大量(1000000 个)递增。
实验过程
标准输入输出
标准输入输出流版本:
#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;
}
输出时间约为 15ms。将 cout << 1 换成 printf("1"),平均时间直接降至 10ms。此时 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。
文件流 (fout) 的表现:
#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 相比速度相当。
加大数据量
输出 100000 个 1:
#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;
}
输出: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;
}
输出:6ms。
为了真正显现出两者速度的差别,加大数据至 1000000 个 1:
| 输出 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 也花了 56 毫秒。测 cout,结果竟是 27 毫秒。测了 10 次,最慢就是 27 毫秒。因此这个数据正式破除了一个言论:cout 永远比 printf 慢。事实证明,它在文件读写更胜一筹。
这次使用的是文件重定向,而文件流 (fout) 的表现则更让人惊讶:仅仅 16 毫秒。
实验结论
数据分析
- 在小数据量情况下,
cout确实慢于printf,cout由于需要维护类型安全并可能与 C 标准流同步,其单次函数调用的开销略高于printf。 - 在文件读写且中小数据量时,两者相差不多,因为重定向和文件流的缓冲区都够大,磁盘 IO 会在关闭文件时统一处理。
- 在大规模文件读写时,
cout和fout更大缓冲区的好处就出来了:真实磁盘 IO 次数少很多。因此它们相对缓冲区小且更保守的printf快。 cout的输出在文件 IO 时还得重定向,fout根本不用,缓冲区策略也激进,磁盘 IO 更少,所以在所有文件测试中都领先。
建议
对于常规 OJ 刷题,推荐 cout(求速度使用 ios::sync_with_stdio(false);,会关闭与 C 标准流的同步);对于需格式化输出的时候,iomanip 内的工具开销较大,所以推荐 printf;正规比赛时,还得用 fout 等,重定向顺手但相对其较慢。
注意:使用 cout 时少用 std::endl,会自动清空缓冲区,多一次 IO。

