freopen stdout 真的更快?
在一次數獨作業中,我發現大部分同學送出的代碼中都使用
freopen
來将
stdout
重新指向目标檔案進行檔案輸出操作。我感到十分好奇,關于
freopen
我幾乎從未用過,也很少在其它地方看到别人使用,也就是說至少我的認知裡該函數不是個常用函數。再來點資料支援:
- 關于
在 Google 中的搜尋結果有636萬條fopen

-
在 Google 中的搜尋結果有35.7萬條,少了一個數量級!freopen
是以我想同學們是不是從哪裡道聽途說了這種用法的好處,或者在某些環境下先入為主而習慣使用
freopen
。我嘗試在班級群中發出疑問,果然兩個原因都有:
助教 L 說:
我看部分同學的說法是:非常快
能夠把檔案輸出的速度提升到極緻
同學 H 說:
以前我記得做c++作業的時候,有這樣的,然後我不懂怎麼弄,大神就教我可以這樣輸出到檔案,至于為什麼。。。我去他宿舍問一下
同學 C 說:
。。。noip都用freopen
關于習慣問題,這裡不做展開。隻是簡單提一下,
freopen
重定向
stdout
會讓一個普通程式的輸出變得麻煩,比如同時讀寫若幹個檔案,同時要輸出到 console 等。
關于性能問題,這個道聽途說就十分不妥,都是做技術的,這樣的小問題很容易動手驗證,那我們就幹一發票試試。
測試環境:Windows 10 / Visual Studio 2015
- 首先,來個函數,對一個連續寫入操作計時:
clock_t do_write(FILE* fp, char* data, size_t len) {
// The clock() function returns an approximation of processor time used by the program.
// The value returned is the CPU time used so far as a clock_t;
// to get the number of seconds used, divide by CLOCKS_PER_SEC.
clock_t clock_begin, clock_end;
clock_begin = clock();
for (int i = 0; i < 1000; ++i) {
auto n = fwrite(data, len, 1, fp);
assert(n == 1);
}
fflush(fp);
clock_end = clock();
return clock_end - clock_begin;
}
clock
計時請看以上代碼注釋
- 然後我們分别以
,freopen
打開檔案,并且寫入 1000MB 看看并輸出耗時:fopen
int main() {
auto data = new char[1048576]; // 1MB
// initialize the buffer
for (int i = 0; i < 1048576; ++i)
data[i] = i;
clock_t elapsed;
auto fp_reopen = freopen("data_freopen.bin", "wb", stdout);
assert(fp_reopen != nullptr);
elapsed = do_write(fp_reopen, data, 1048576);
// redirect stdout to console
#ifdef _WIN32
freopen("CONOUT$", "w", stdout);
#else
freopen("/dev/tty", "w", stdout);
#endif
printf("write with freopen clocks elapsed: %zu\n", elapsed);
auto fp = fopen("data_fopen.bin", "wb");
assert(fp != nullptr);
elapsed = do_write(fp, data, 1048576);
fclose(fp);
printf("write with fopen clocks elapsed: %zu\n", elapsed);
delete[] data;
}
測試輸出:
write with freopen clocks elapsed: 1644
write with fopen clocks elapsed: 8855
好家夥,果然快很多!但是!為!什!麼!?
難道是兩種方式打開檔案的緩存機制不同?
- 那行,讓它們使用同樣的緩存:
setvbuf
可以辦到!如果不了解,請看這裡:http://en.cppreference.com/w/c/io/setvbuf
auto cache = new char[512 * 1024];
auto fp_reopen = freopen("data_freopen.bin", "wb", stdout);
assert(fp_reopen != nullptr);
setvbuf(fp_reopen, cache, _IOFBF, 512 * 1024);
...
auto fp = fopen("data_fopen.bin", "wb");
assert(fp != nullptr);
setvbuf(fp, cache, _IOFBF, 512 * 1024);
...
}
write with freopen clocks elapsed: 1761
write with fopen clocks elapsed: 9146
依!然!如!此!呆坐中。。。
- 連續寫入大量資料
- 設定相同的緩存機制
還能有什麼影響呢?
- runtime library
- 作業系統
- 檔案系統
- 磁盤硬體
想想我們拷貝大檔案的現象,一般都是起步很快,然後會下降到一個較穩定的值上下徘徊,這個原因比較明顯,系統及硬體都提供了一定的緩存。
- 剛開始緩存空閑,資料都飛快寫入緩存
- 同時緩存也不停地在刷入磁盤
- 因為連續寫入大量資料,磁盤本身很慢,緩存逐漸被填滿,這時候寫入緩存也需要等待(現象就是寫入速度下降到刷磁盤的速度)
那行了,我們測試是寫2個檔案,一個先一個後,并且是連續操作,也就是說先寫的檔案優先享受了緩存帶來的好處,後寫的檔案沒有了這個優勢。思考完,做個驗證:
// 先測 fopen
auto fp = fopen("data_fopen.bin", "wb");
assert(fp != nullptr);
setvbuf(fp, cache, _IOFBF, 512 * 1024);
elapsed = do_write(fp, data, 1048576);
fclose(fp);
printf("write with fopen clocks elapsed: %zu\n", elapsed);
// 再測 freopen
auto fp_reopen = freopen("data_freopen.bin", "wb", stdout);
assert(fp_reopen != nullptr);
setvbuf(fp_reopen, cache, _IOFBF, 512 * 1024);
elapsed = do_write(fp_reopen, data, 1048576);
// redirect stdout to console
#ifdef _WIN32
freopen("CONOUT$", "w", stdout);
#else
freopen("/dev/tty", "w", stdout);
#endif
printf("write with freopen clocks elapsed: %zu\n", elapsed);
write with fopen clocks elapsed: 1561
write with freopen clocks elapsed: 9267
哈哈!答案揭曉!
freopen stdout
并沒有性能上的優勢!
- 進一步做驗證,我們依然按照
freopen
的順序來測試,但是在兩次測試中間加上fopen
讓緩存能空閑出來。這裡就不貼代碼了,直接上結果:sleep
write with freopen clocks elapsed: 2326
write with fopen clocks elapsed: 2519
結論
要動手驗證!驗證!驗證!而不是道聽途說!
Linux也做過測試,結論也一樣!
參考
劉未鵬 - 遇到問題為什麼應該自己動手