天天看點

freopen stdout 真的更快?

freopen stdout 真的更快?

在一次數獨作業中,我發現大部分同學送出的代碼中都使用

freopen

來将

stdout

重新指向目标檔案進行檔案輸出操作。我感到十分好奇,關于

freopen

我幾乎從未用過,也很少在其它地方看到别人使用,也就是說至少我的認知裡該函數不是個常用函數。再來點資料支援:

  • 關于

    fopen

    在 Google 中的搜尋結果有636萬條
freopen stdout 真的更快?
  • freopen

    在 Google 中的搜尋結果有35.7萬條,少了一個數量級!
freopen stdout 真的更快?

是以我想同學們是不是從哪裡道聽途說了這種用法的好處,或者在某些環境下先入為主而習慣使用

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

    ,

    fopen

    打開檔案,并且寫入 1000MB 看看并輸出耗時:
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也做過測試,結論也一樣!

參考

劉未鵬 - 遇到問題為什麼應該自己動手