C++ cin、cout的优化及测评

    参加过ACM比赛和部分自己做过测评的人想必不难发现,C++的cin和cout虽然比起C的scanf和printf来方便很多,但是速度似乎会打很大折扣。于是不求甚解的人们一拍脑门,认定因为C更“底层”,封装程度更低,所以势必比C++的输入输出执行效率更高。然而如若C++有知,恐怕要苦笑了。因为C++正是为了兼容C的输入输出才要在cin、cout中进行指针同步,降低了执行效率。那么可能挽回这种效率损失吗?答案是可以。

    C++中提供了解除指针同步的方法:ios::sync_with_stdio(false)。如字面意思,这个方法用来设置输入输出流是否与C的stdio同步。那么解除同步之后能有多大效果呢?毕竟C++确实因为封装程度比C高多多少少损失了一些执行效率。我动手进行了一系列测评,下面是源码。

    这里我统一对输入输出做了重定向,不论是输入数据来源还是输入数据目标都是“data”文件。所有的输入/输出测试都是读或写10^7次字符串与整型数的组合数据,每种输入/输出测试都进行10次并取平均耗时作为评估值。

    首先是C的输入输出。

    scanf.cpp:

#include <cstdio>
#include <ctime>

int main()
{
    int testCnt = 10;
    int caseCnt = 10000000;
    double t_sum = 0;
    freopen("data", "r", stdin);
    for(int j = 0 ; j < testCnt ; j++)
    {
        long t_start = clock();
        char s[20];
        for(int i = 0 ; i < caseCnt ; i++)
        {
            scanf("%s", s);
        }
        long t_end = clock();
        t_sum += (double)(t_end - t_start) / CLOCKS_PER_SEC;
    }
    printf("average cost: %lfs\n", t_sum/testCnt);
    fclose(stdin);
    return 0;
}

    printf.cpp:

#include <cstdio>
#include <ctime>

int main()
{
    int testCnt = 10;
    int caseCnt = 10000000;
    double t_sum = 0;
    freopen("data", "w", stdout);
    for(int j = 0 ; j < testCnt ; j++)
    {
        long t_start = clock();
        for(int i = 0 ; i < caseCnt ; i++)
        {
            printf("string%d\n", i);
        }
        long t_end = clock();
        t_sum += (double)(t_end - t_start) / CLOCKS_PER_SEC;
    }
    printf("average cost: %lfs\n", t_sum/testCnt);
    fclose(stdout);
    return 0;
}

    然后是不解锁同步的cin、cout方法。

    cin.cpp:

#include <iostream>
#include <cstdio>
#include <ctime>
#include <string>
#include <fstream>

using namespace std;

int main()
{
    int testCnt = 10;
    int caseCnt = 10000000;
    double t_sum = 0;
    ifstream fin("data");
    streambuf *_cin =  cin.rdbuf(fin.rdbuf());
    for(int j = 0 ; j < testCnt ; j++)
    {
        long t_start = clock();
        string s;
        for(int i = 0 ; i < caseCnt ; i++)
        {
            cin >> s;
        }
        long t_end = clock();
        t_sum += (double)(t_end - t_start) / CLOCKS_PER_SEC;
    }
    cout << "average cost: " << t_sum/testCnt << "s\n";
    cin.rdbuf(_cin);
    fin.close();
    return 0;
}

    cout.cpp:

#include <iostream>
#include <ctime>
#include <string>
#include <fstream>

using namespace std;

int main()
{
    int testCnt = 10;
    int caseCnt = 10000000;
    double t_sum = 0;
    ofstream fout("data");
    streambuf *_cout =  cout.rdbuf(fout.rdbuf());
    for(int j = 0 ; j < testCnt ; j++)
    {
        long t_start = clock();
        for(int i = 0 ; i < caseCnt ; i++)
        {
            cout << "string" << i << '\n';
        }
        long t_end = clock();
        t_sum += (double)(t_end - t_start) / CLOCKS_PER_SEC;
    }
    cout << "average cost: " << t_sum/testCnt << "s\n";
    cout.rdbuf(_cout);
    fout.close();
    return 0;
}

    接下来是解锁同步的cin、cout方法。

    optimized_cin.cpp:

#include <iostream>
#include <cstdio>
#include <ctime>
#include <string>
#include <fstream>

using namespace std;

int main()
{
    int testCnt = 10;
    int caseCnt = 10000000;
    double t_sum = 0;
    ios::sync_with_stdio(false);
    ifstream fin("data");
    streambuf *_cin =  cin.rdbuf(fin.rdbuf());
    for(int j = 0 ; j < testCnt ; j++)
    {
        long t_start = clock();
        string s;
        for(int i = 0 ; i < caseCnt ; i++)
        {
            cin >> s;
        }
        long t_end = clock();
        t_sum += (double)(t_end - t_start) / CLOCKS_PER_SEC;
    }
    cout << "average cost: " << t_sum/testCnt << "s\n";
    cin.rdbuf(_cin);
    fin.close();
    return 0;
}

    optimized_cout.cpp:

#include <iostream>
#include <ctime>
#include <string>
#include <fstream>

using namespace std;

int main()
{
    int testCnt = 10;
    int caseCnt = 10000000;
    double t_sum = 0;
    ios::sync_with_stdio(false);
    ofstream fout("data");
    streambuf *_cout =  cout.rdbuf(fout.rdbuf());
    for(int j = 0 ; j < testCnt ; j++)
    {
        long t_start = clock();
        for(int i = 0 ; i < caseCnt ; i++)
        {
            cout << "string" << i << '\n';
        }
        long t_end = clock();
        t_sum += (double)(t_end - t_start) / CLOCKS_PER_SEC;
    }
    cout << "average cost: " << t_sum/testCnt << "s\n";
    cout.rdbuf(_cout);
    fout.close();
    return 0;
}

    同时测试C++直接读写文件的方法。

    fin.cpp:

#include <iostream>
#include <cstdio>
#include <ctime>
#include <string>
#include <fstream>

using namespace std;

int main()
{
    int testCnt = 10;
    int caseCnt = 10000000;
    double t_sum = 0;
    ifstream fin("data");
    for(int j = 0 ; j < testCnt ; j++)
    {
        long t_start = clock();
        string s;
        for(int i = 0 ; i < caseCnt ; i++)
        {
            fin >> s;
        }
        long t_end = clock();
        t_sum += (double)(t_end - t_start) / CLOCKS_PER_SEC;
    }
    cout << "average cost: " << t_sum/testCnt << "s\n";
    fin.close();
    return 0;
}

    fout.cpp:

#include <iostream>
#include <ctime>
#include <string>
#include <fstream>

using namespace std;

int main()
{
    int testCnt = 10;
    int caseCnt = 10000000;
    double t_sum = 0;
    ofstream fout("data");
    for(int j = 0 ; j < testCnt ; j++)
    {
        long t_start = clock();
        for(int i = 0 ; i < caseCnt ; i++)
        {
            fout << "string" << i << '\n';
        }
        long t_end = clock();
        t_sum += (double)(t_end - t_start) / CLOCKS_PER_SEC;
    }
    cout << "average cost: " << t_sum/testCnt << "s\n";
    fout.close();
    return 0;
}

    下面是测试结果:

scanf与printfcin与cout优化的cin与cout fin与fout
输入1.125694s2.46949s0.950055s0.834976s
输出1.304706s1.46059s1.41783s1.41298s

    可以看到,事实上,解开同步之前,cin耗时是scanf的两倍还多,cout则比printf略慢。解开枷锁的cin执行效率与直接读文件接近,甚至比C的scanf还要快。至于输出,解开同步的cout执行效率略有提高,同样接近直接写文件,但是确实还是比C的printf略慢一点。

Fork me on GitHub