cout和printf的速度实验

· · 休闲·娱乐

注:1.11改为休闲·娱乐,因为投不进去。

输出,是一项很重要的事。一切比赛以输出为基础,但高等级的 OI 输出太大,一不留神就 \textcolor{#0000A1}{TLE}。因此,我们要学习如何更快输出,毕竟没人愿意比赛因为输出过大而超时。

实验起因

洛谷有一种很少见的评测状态,叫 \textcolor{#0000A1}{OLE}。意思是输出超限。 举个 OLE 代码的例子:

#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 低。

但这只是传言,不一定是对的(没错,后面,我推翻了它)。 于是,我开始了一次实验。

实验设计

为了公平比较,保证所有测试均遵循以下条件:

实验过程

标准输入输出

标准输入输出流版本:

#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 相比还是很快的。

加大数据量

换成输出 1000001

#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

时间依然很少,代表测试规模依然不够。

为了真正显现出两者速度的差别,我们加大数据,让它输出 100000001

11111111111111111111······(后略)
time:56ms

这一次,即使是 printf 也花了 56 毫秒。 测测 cout,结果竟是27 毫秒

测了 10 次,最慢就是 27 毫秒。因此这个数据正式破除了一个言论:cout 永远比 printf 慢。事实证明,它在文件读写更胜一筹。

这次使用的是文件重定向,而文件流的表现则更让人惊讶:仅仅 16 毫秒

实验结论

数据分析:

具体数据如下表: 输出 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不是能一下能输出完的,测不了。