算法竞赛中cin/cout和scanf/printf的耗时对比

在一次无意的做题提交中,使用cin导致TLE(Time Limit Exceeded),而使用scanf没有导致TLE,而引发了我对cin和scanf的思考,由此产生了这篇文章

cin/cout和scanf/printf速度对比

在windows平台上测试会有较大差距,例如在code blocks上,cin读取的时间总是远远小于scanf,而在visual studio 2022中,cin读取的时间和scanf相差无几,因此,本人测试是在manjaro linux系统下,使用g++编译器得到的结果

具体测试代码:

#include <iostream>
using namespace std;
#define TEST "一百万.txt"
const int MAXN = (int)1e6;

void scanf_read(void){
    freopen(TEST, "r", stdin);
    for (int i = 0; i < MAXN; i++)
        scanf("%d", &numbers[i]);
}
void cin_read(void){
    ios::sync_with_stdio(false);
    cin.tie(0);
    freopen(TEST, "r", stdin);
    for (int i = 0; i < MAXN; i++)
        cin >> numbers[i];
}
void printf_show(void){
    for (int i = 0; i < MAXN; i++)
        printf("%d\n", numbers[i]);
}
void coutn_show(void){
    ios::sync_with_stdio(false);
    for (int i = 0; i < MAXN; i++)
        cout<< numbers[i] << '\n';
}
void coutendl_show(void){
    ios::sync_with_stdio(false);
    for (int i = 0; i < MAXN; i++)
        cout << numbers[i] << endl;
}
int main(){
    int start = clock();
    scanf_read();
    printf("%.3lf\n", double(clock() - start) / CLOCKS_PER_SEC);//注意在使用禁用同步后,请将该printf替换为用cout输出
}

cin与scanf对比

测试一(网友测试数据):

经过用于读取标准输入的数字列表和XOR所有数字测试程序的测试,得出结论:

iostream version:                       21.9 seconds    //使用cin
scanf version:                           6.8 seconds    //使用scanf
iostream with sync_with_stdio(false):    5.5 seconds    //关掉C++ I/O功能 保持 与C I/O功能的同步

//关闭功能同步代码
std::ios::sync_with_stdio(false);//函数介绍http://www.cplusplus.com/reference/ios/ios_base/sync_with_stdio/

测试二(我在linux下测试):

//1e7数据规模下各方法读取时间
    scanf("%d", &numbers[i]);       0.69s
    cin >> numbers[i];              2.20s

//仅关闭同步
    ios::sync_with_stdio(false);    0.71s

//关闭同步且使用cin.tie(0)
    ios::sync_with_stdio(false);
    cin.tie(0);                     0.67s

显而易见,cin赢了! 事实证明,这种内部同步/刷新通常会减慢iostream i/o的速度。如果我们不混合stdio和iostream,我们可以关闭它,然后cin是最快的。

比较合理的解释:默认情况,cin与stdin总是保持同步的,也就是说这两种方法可以混用,而不必担心文件指针混乱,同时cout和stdout也一样,两者混用不会输出顺序错乱。正因为这个兼容性的特性,导致cin有许多额外的开销,如何禁用这个特性呢?只需一个语句 std::ios::sync_with_stdio(false);,这样就可以取消cin于stdin的同步了,此时的cin就与scanf差不多 了。

另一种解释: cout在输出时总是要先将输出的存入缓存区。而printf直接调用系统进行IO,它是非缓存的。所以cout比printf慢。(这种解释,我没有验证)

相关网站

探寻C++最快的读取文件的方案

cout和printf对比

先介绍用到的方法

ends:       终止字符串,即在末尾加入“\0”或者空白字符
flush:      刷新缓冲区
endl:       换行加刷新输出缓冲区

测试结果

//5e6数据规模输出测试
    printf("%d\n", numbers[i]);     7.89s
    cout+'\n'                       8.16s
    cout+endl                       7.89s
    cout+ends                       0.97s

//使用ios::sync_with_stdio(false);
    cout+'\n'                       3.19s
    cout+endl                       8.09s
    cout+ends                       0.75s

//使用ios::sync_with_stdio(false);   cout.tie(0);
    cout+'\n'                       3.10s
    cout+endl                       7.69s
    cout+ends                       0.81s

总结

在数据规模大的情况下,没有禁用iostrema I/Ostdio I/O的同步情况下

  • cin和scanf对比
    • 未禁用同步
      推荐使用scanf,scanf的读取速度明显高于cin
    • 禁用同步
      推荐使用cin,在C++编程中尽量少使用C语言的代码
    ios::sync_with_stdio(false);    0.71s
    
    ios::sync_with_stdio(false);
    cin.tie(0);                     0.67s
    

    两者速度对比具有cin.tie(0);代码的更快,因此建议写cin.tie(0);

  • cout和printf对比

    • 未禁用同步
      两者速度基本相同,用谁都行,但是,在C++中也要尽量少使用C语言代码
    • 禁用同步

    推荐使用cout + '\n'形式,使用cout + endl的话,速度与printf相差无几,而禁用同步时间则会大大减小

    //ios::sync_with_stdio(false);
        cout+'\n'                       3.19s
        cout+endl                       8.09s
        cout+ends                       0.75s
    //使用ios::sync_with_stdio(false);   cout.tie(0);
        cout+'\n'                       3.1s
        cout+endl                       7.69s
        cout+ends                       0.81s
    

    两者速度对比cout.tie(0);更快,因此建议写cout.tie(0);

sync_with_stdio()

这个函数是一个“是否兼容stdio”的开关,C++为了兼容C,保证程序在使用了std::printf和std::cout的时候不发生混乱,将输出流绑到了一起。

tie()

tie是将两个stream绑定的函数,空参数的话返回当前的输出流指针。在默认的情况下cin绑定的是cout,每次执行 << 操作符的时候都要调用flush,这样会增加IO负担。可以通过tie(0)(0表示NULL)来解除cin与cout的绑定,进一步加快执行效率。

ACM应用

在ACM里,经常出现数据集超大造成 cin TLE的情况。这时候大部分人认为这是cin的效率不及scanf的错,甚至还上升到C语言和C++语言的执行效率层面的无聊争论。其实像上文所说,这只是C++为了兼容而采取的保守措施。我们可以在IO之前将stdio解除绑定,这样做了之后要注意不要同时混用cout和printf之类。

在默认的情况下cin绑定的是cout,每次执行 << 操作符的时候都要调用flush,这样会增加IO负担。可以通过tie(0)(0表示NULL)来解除cin与cout的绑定,进一步加快执行效率。

ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);

原来而cin,cout之所以效率低,是因为先把要输出的东西存入缓冲区,再输出,导致效率降低,而这段代码可以来打消iostream的输入 输出缓存,可以节省许多时间,使效率与scanf与printf相差无几.

说白了,这两句加上就可以提高C++代码输入输出执行效率使得和C相差无几,甚至优于C

作者:WuQiling
文章链接:https://www.wqlblog.cn/algorithm-contest-cin-cout-and-scanf-printf-speed-comparison/
文章采用 CC BY-NC-SA 4.0 协议进行许可,转载请遵循协议

评论

  1. 博主 置顶
    Windows Edge
    2 年前
    2023-2-22 22:22:58

    找到了一个使用cin/cout但不使用scanf/printf以及sync_with_stdio()函数会导致超时的例子
    1249. 亲戚 – AcWing题库

发送评论 编辑评论


				
默认
贴吧
上一篇
下一篇