提高C++的cin/cout效率

cin.tie與sync_with_stdio加速輸入輸出

以前碰到cin TLE的時(shí)候總是傻乎乎地改成scanf,甚至還相信過(guò)C++在IO方面效率低下的鬼話(huà),殊不知這只是C++為了兼容C而采取的保守措施。

tie

tie是將兩個(gè)stream綁定的函數(shù),空參數(shù)的話(huà)返回當(dāng)前的輸出流指針。

#include <iostream>
#include <fstream> 
///////////////////////////SubMain//////////////////////////////////
int main(int argc, char *argv[])
{
    std::ostream *prevstr;
    std::ofstream ofs;
    ofs.open("test.txt");
    std::cout << "tie example:\n";  // 直接輸出到屏幕
    *std::cin.tie() << "This is inserted into cout\n";  
    // 空參數(shù)調(diào)用返回默認(rèn)的output stream,也就是cout  
    prevstr = std::cin.tie(&ofs);   
    // cin綁定ofs,返回原來(lái)的output stream  
    *std::cin.tie() << "This is inserted into the file\n";  
    // ofs,輸出到文件    
    std::cin.tie(prevstr);                              
    // 恢復(fù) 
    ofs.close();
    system("pause");
    return 0;
}
///////////////////////////End Sub//////////////////////////////////

輸出:

tie example:
This is inserted into cout請(qǐng)按任意鍵繼續(xù). . .

同時(shí)當(dāng)前目錄下的test.txt輸出:

This is inserted into the file

sync_with_stdio

這個(gè)函數(shù)是一個(gè)“是否兼容stdio”的開(kāi)關(guān),C++為了兼容C,保證程序在使用了std::printf和std::cout的時(shí)候不發(fā)生混亂,將輸出流綁到了一起。

應(yīng)用

在A(yíng)CM里,經(jīng)常出現(xiàn)數(shù)據(jù)集超大造成 cin TLE的情況。這時(shí)候大部分人(包括原來(lái)我也是)認(rèn)為這是cin的效率不及scanf的錯(cuò),甚至還上升到C語(yǔ)言和C++語(yǔ)言的執(zhí)行效率層面的無(wú)聊爭(zhēng)論。其實(shí)像上文所說(shuō),這只是C++為了兼容而采取的保守措施。我們可以在IO之前將stdio解除綁定,這樣做了之后要注意不要同時(shí)混用cout和printf之類(lèi)。

在默認(rèn)的情況下cin綁定的是cout,每次執(zhí)行 << 操作符的時(shí)候都要調(diào)用flush,這樣會(huì)增加IO負(fù)擔(dān)??梢酝ㄟ^(guò)tie(0)(0表示NULL)來(lái)解除cin與cout的綁定,進(jìn)一步加快執(zhí)行效率。

如下所示:

#include <iostream>
int main() 
{
    std::ios::sync_with_stdio(false);   
    std::cin.tie(0);    // IO
}

reference:

http://meme.biology.tohoku.ac.jp/students/iwasaki/cxx/speed.html

C++的輸出入cin/cout和scanf/printf誰(shuí)比較快?

有打過(guò)資訊競(jìng)賽的人,一定有遇過(guò)用cin/cout結(jié)果TLE,換成scanf/printf就AC的情況。
難道cin/cout真的比較慢嗎?為什麼C++要做出一個(gè)比C還要更慢的輸入輸出介面呢?

我們來(lái)看看cin/cout的效率到底怎麼樣。

以下都是個(gè)人的觀(guān)察,有錯(cuò)的話(huà)請(qǐng)留言告知QAQ,本人很廢還請(qǐng)鞭小力一點(diǎn)。

開(kāi)始前,cin/cout是什麼?

首先,我們先來(lái)看一下cin/cout和scanf/printf的差別,前者是物件,後者是函數(shù)。
函數(shù)很簡(jiǎn)單,就是定義一個(gè)函數(shù),然後他會(huì)把裡面出現(xiàn)%的地方取代掉,而物件則是重載了shift運(yùn)算子<<,>>,其實(shí)真的很直觀(guān),就丟進(jìn)cout跟從cin拿出來(lái)嘛~,而且也不用管型別,因?yàn)榫幾g器會(huì)幫你找運(yùn)算子規(guī)則。
這裡我們發(fā)現(xiàn),型別是編譯器處理的,和執(zhí)行時(shí)完全沒(méi)有關(guān)係(別再說(shuō)cin/cout慢是因?yàn)橐袛嘈蛣e了),而且自由度更高,可以自己定義。

那cin/cout到底慢再哪裡呢?
我們先用time指令做個(gè)小實(shí)驗(yàn),在Ubuntu 14.04筆電對(duì)一個(gè)檔案寫(xiě)入1e7的random整數(shù),這裡的程式碼都是簡(jiǎn)化的code。

開(kāi)始實(shí)驗(yàn)

for(int i = 0; i < (int)1e7; i++){
    printf("%d\n",rand());
}
// vs
for(int i = 0; i < (int)1e7; i++){
    cout<<rand()<<endl;
}

實(shí)驗(yàn)三次,printf的時(shí)間分別是,
1.760 s
2.677 s
1.865 s

看起來(lái)很優(yōu)秀,那cout呢?
15.921 s
15.188 s
15.685 s

發(fā)生了什麼事?怎麼慢成這樣!

優(yōu)化1:sync_with_stdio 函數(shù):和stdio同步

我已經(jīng)看到那些篤定cin/cout不好的人偷笑的表情了,但是事情別說(shuō)的太早,我們先看一下C++ Reference對(duì)於cin/cout的說(shuō)明,我們發(fā)現(xiàn)了一個(gè)函數(shù):std::ios_base::sync_with_stdio(false),他是這樣說(shuō)的,

Toggles on or off synchronization of all the iostream standard streams with their corresponding standard C streams if it is called before the program performs its first input or output operation.

If called once an input or output operation has occurred, its effects are implementation-defined.

By default, iostream objects and cstdio streams are synchronized (as if this function was called with true as argument).
With stdio synchronization turned off, iostream standard stream objects may operate independently of the standard C streams (although they are not required to), and mixing operations may result in unexpectedly interleaved characters.

看起來(lái),cin/cout預(yù)設(shè)必須要跟stdin/stdout同步,所以必須做額外的運(yùn)算,注意要是關(guān)掉了,scanf/printf就不能用了(如果用了,而且跟cin/cout混用,可能會(huì)吃到奇怪的東西),那我們?cè)囍阉P(guān)掉看看。

ios_base::sync_with_stdio(false);
for(int i = 0; i < (int)1e7; i++){
    cout<<rand()<<endl;
}

結(jié)果:
13.120 s
14.958 s
15.165 s

看起來(lái)變快了兩秒,甚至根本沒(méi)變快,還是很慢啊…(你看看,自己慢還怪scanf/printf拖慢你)
等等,我們還忘了一個(gè)東西,endl。

優(yōu)化2:endl 和 flush 物件:cout的緩衝區(qū)優(yōu)化

什麼是endl,他是一個(gè)定義好的物件,在cout上給cout換行用的,那他跟<<’\n’有什麼差別呢?
原來(lái),cout用了一個(gè)類(lèi)似優(yōu)化的設(shè)計(jì),叫作緩衝區(qū)(由作業(yè)系統(tǒng)實(shí)作),所有的輸出都會(huì)先進(jìn)到緩衝區(qū)裡,直到緩衝區(qū)滿(mǎn)了才會(huì)清空緩衝區(qū)並把字串輸出到stdout之類(lèi)的輸出串流,難怪沒(méi)有跟stdout同步會(huì)出錯(cuò)。
而當(dāng)一般人寫(xiě)程式的時(shí)候,輸出當(dāng)然希望程式會(huì)把東西印到螢?zāi)簧?,但是如果緩衝區(qū)還沒(méi)滿(mǎn),我們就看不到結(jié)果了!
怎麼辦呢?cout有一個(gè)物件叫作flush(用法跟endl一樣),做的事情就是強(qiáng)迫清空緩衝區(qū),並輸出到串流。
但是為什麼平常初學(xué)C++的人都沒(méi)有打過(guò)flush呢?原因有幾個(gè),一個(gè)是Windows8以前的Windows CMD會(huì)自動(dòng)清空緩衝區(qū)(或是根本沒(méi)有QAQ),另外一個(gè)主要的原因就是,其實(shí)endl就是<<’\n’<<flush;,對(duì),endl就是換行加上flush,也就是說(shuō),如果我們用endl的話(huà),就會(huì)強(qiáng)迫每個(gè)數(shù)字都清空緩衝區(qū),累積一定量再一起輸出對(duì)cout來(lái)說(shuō)可以?xún)?yōu)化一些操作,而這樣就破壞了這個(gè)優(yōu)化了,我們?cè)囍裡ndl拿掉試試看。

ios_base::sync_with_stdio(false);
for(int i = 0; i < (int)1e7; i++){
    cout<<rand()<<'\n';
}

結(jié)果:
2.765 s
1.708 s
1.713 s

太震驚了,去掉了endl之後,cout的速度已經(jīng)和printf差不多快了!!整整快了12秒??!
原來(lái)效率就是在這種情況下不見(jiàn)的,那為什麼要作endl這種物件呢?
我們看看下面的實(shí)驗(yàn)。

附註,其實(shí)printf也是有緩衝區(qū)的,只是他預(yù)設(shè)是到滿(mǎn)了才會(huì)清空。平常在console可以看到輸出是因?yàn)镺S幫忙我們把緩衝區(qū)清掉了

優(yōu)化3:cin.tie(0):cin和cout綁定

我們先吃一個(gè)數(shù)字進(jìn)來(lái),再把他輸出。

for(int i = 0; i < (int)1e7; i++){
    scanf("%d\n",&a);
    printf("%d\n",a+1); //output a+1;
}
// vs
for(int i = 0; i < (int)1e7; i++){
    cin>>a;
    cout<<a+1<<endl;
}

scanf/printf的時(shí)間:
2.579 s
3.994 s
3.241 s

而cin/cout:
19.970 s

不意外,那加上關(guān)閉同步的話(huà)?

ios_base::sync_with_stdio(false);
for(int i = 0; i < (int)1e7; i++){
    cin>>a;
    cout<<a+1<<endl;
}

結(jié)果:
16.575 s

快了幾秒,不算太意外,那去掉endl呢?

ios_base::sync_with_stdio(false);
for(int i = 0; i < (int)1e7; i++){
    cin>>a;
    cout<<a+1<<'\n';
}

結(jié)果:
16.408 s

什麼??!完全沒(méi)有變快啊???(你看看,看來(lái)就算cout很快,cin還是很慢啊)
等等,已經(jīng)說(shuō)過(guò)cin沒(méi)道理比scanf慢這麼多,所以我們來(lái)看看發(fā)生了什麼事。
既然和有endl一樣快,我們可以合理懷疑是cin/cout又清空緩衝區(qū)了。
我們?cè)囋嚳聪旅娴睦?,我們先吃進(jìn)一個(gè)陣列,再丟出來(lái)。

ios_base::sync_with_stdio(false);
for(int i = 0; i < (int)1e7; i++){
    cin>>A[i];
}
for(int i = 0; i < (int)1e7; i++){
    cout<<A[i]+1<<'\n';
}

結(jié)果:
2.918 s
2.811 s
3.062 s

太神奇了,竟然變得甚至比scanf/printf還要快了,發(fā)生了什麼事?
看起來(lái)是cin/cout交錯(cuò)使用導(dǎo)致的,我們看一下C++ Reference對(duì)於cin的說(shuō)明,我們發(fā)現(xiàn)一個(gè)函數(shù)tie()。

std::ios::tie
Get/set tied stream
The tied stream is an output stream object which is flushed before each i/o operation in this stream object.

這樣就清楚了,cin預(yù)設(shè)綁住了cout,而被綁住的ostream會(huì)在istream要輸入時(shí)被flush。
那我們?cè)囋嚳窗裞in/cout解綁,我們可以透過(guò)傳一個(gè)NULL(也可以用0)進(jìn)入cin.tie()來(lái)讓cin綁住空的ostream。
我們加上一行cin.tie(0)再來(lái)看剛剛的例子。

ios_base::sync_with_stdio(false);
cin.tie(0);
for(int i = 0; i < (int)1e7; i++){
    cin>>a;
    cout<<a+1<<'\n';
}

結(jié)果:
2.956 s
2.889 s
3.509 s

時(shí)間已經(jīng)和吃進(jìn)陣列差不多了,剩下的差距已經(jīng)在誤差範(fàn)圍內(nèi)了。

為什麼要有tie這個(gè)設(shè)計(jì)呢?
我曾經(jīng)看過(guò)一些說(shuō)法,一種是說(shuō),因?yàn)槲覀冇袝r(shí)候可能要寫(xiě)一些console應(yīng)用程式,如果我們要使用者輸入一些值的時(shí)候可能要先輸出一些提示訊息像是「請(qǐng)輸入一個(gè)數(shù)字:」然後才用cin輸入,要是上面那一句話(huà)沒(méi)有被flush到螢?zāi)簧系脑?huà),使用者就看不到了,而且你可能不想要換行,就算加<<flush也很麻煩,所以C++就設(shè)計(jì)了這樣的作法,讓你在cin前會(huì)把cout清空緩衝區(qū)。

無(wú)論如何,如果我們要對(duì)檔案輸入輸出很顯然不需要這樣,所以就解綁吧!

總結(jié)

我們?cè)囍褦?shù)字範(fàn)圍放大到1e8看看,

scanf/printf:
30.722 s
29.428 s

cin/cout:
27.052 s
27.097 s

cin/cout的表現(xiàn)已經(jīng)比scanf/printf好了,事實(shí)上,我之前看過(guò)一篇文章(現(xiàn)在找不到了QQ)裡面有一張圖表,上面顯示了cin/cout的效率在1e7之後就會(huì)開(kāi)始超越scanf/printf了,當(dāng)然這有很多的因素在裡面,而且iostream使用的記憶體也比scanf/printf高出一些。

但總結(jié)來(lái)說(shuō)cin/cout和scanf/printf比起來(lái)更快最主要的原因,是cin/cout可以在編譯時(shí)期就把型別等等編譯進(jìn)去,而scanf/printf則要在執(zhí)行時(shí)期處理,所以cin/cout就算比scanf/printf快,我覺(jué)得也不會(huì)很奇怪。

參考

[1] http://www.hankcs.com/program/cpp/cin-tie-with-sync_with_stdio-acceleration-input-and-output.html

[2] http://chino.taipei/note-2016-0311C-%E7%9A%84%E8%BC%B8%E5%87%BA%E5%85%A5cin-cout%E5%92%8Cscanf-printf%E8%AA%B0%E6%AF%94%E8%BC%83%E5%BF%AB%EF%BC%9F/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 自然衝擊療法由丁愚仁老師發(fā)明,又稱(chēng)「禪拍」,「拍打」,"自然拍打"。 丁師及其團(tuán)隊(duì)總結(jié)經(jīng)驗(yàn),不同的各種病癥(含絕癥...
    YouAreMyMusic閱讀 2,667評(píng)論 0 4
  • 今天要給大家分享的書(shū)是托馬斯戈登博士寫(xiě)的「PET父母效能訓(xùn)練手冊(cè)」,這本書(shū)的作者托馬斯戈登博士曾於1997、199...
    zoewyc閱讀 363評(píng)論 0 0
  • 一一與旋轉(zhuǎn)屋 徐空文 (這是十幾年前創(chuàng)作的第一個(gè)劇本,雖然幼稚,但現(xiàn)在看來(lái)竟是我最喜歡的劇本之一,雖然之後曾以寫(xiě)劇...
    徐空文閱讀 710評(píng)論 0 5
  • 胡益達(dá)閱讀 471評(píng)論 0 1
  • 夕留光陰_2c58閱讀 286評(píng)論 0 0

友情鏈接更多精彩內(nèi)容