I/O 優化

有的時候會在題目中看到這麼一句:「建議使用 scanf/printf,使用 cincout 可能造成 TLE。」在輸入、輸出量比較大的題目,應該都可以明顯感受到 stdioiostream 兩個的差異,scanfprintf 的效率顯然高得多,但是事情並不是這樣。

cincout 的好處很明顯,像是它們保證類型安全,不會像 printfscanf 有打錯類型的風險,且 <<>> 這兩個運算子可以重載,換句話說,你可以在定義一個類型的物件 a 如何輸入輸出後,方便的使用 cin >> acout << a,就可以做到輸入輸出,既方便又直觀。

再加上 cout 像是補 0、控制位數等等都比 printf 好寫得多,所以事實上 cincout 是比較方便的,然而,速度似乎慢很多?先來看看為什麼:

在文字被輸出前,它會先到緩衝區,不是馬上就全部輸出,而是到一個特定的時間再一次輸出出去,因為分次輸出其實算是很耗資源的事。

緩衝區的 flush 是決定效率的關鍵因素,看是一次 flush、還是多次 flush,速度就會大有不同。printf 是不會自動 flush 的,scanf 也不會強制 flush,但 cin 就會了,因為在要求使用者輸入之前,應該先讓使用者看見先前輸出的文字,這是介面設計上的考量,但顯然不符合競程的需要,因此我們要取消強制 flush:

1
cin.tie(0);
tieios 的一個方法,也就是說,cincerrcout 都有這個方法,cincerr 預設是有 tie cout 的,所以在使用 cincerr 時,cout 的緩衝區會先被釋放,tie(0) 就可以解除。所以如果你不想看到 cerrcout 的訊息全部卡在一起,可以再加上 cerr.tie(0)

接下來,還有一個地方會自動 flush,就是 endl,也就是說,cout << endl 其實是 cout << flush << '\n',解決方法很簡單,就是不要用,都用 cout << '\n' 就可以了。

最後,還有一個跟緩衝區無關的問題,C++ 中有兩種輸入輸出系統:stdioiostream,因為一些歷史因素,C++ 會讓 iostreamstdio 同步,來避免混用時發生無法預期的問題,所以這個額外的動作會造成效率降低,不過打競程時不會有混用的狀況,把同步解除就好:

1
ios_base::sync_with_stdio(false);
(記得這個用了之後,iostreamstdio 不能混用。)