FrontPage

ファイル入出力のセオリー

プログラム言語でファイルを扱うのにはいくつかセオリーがあると考えています。
なにせハードディスクへのアクセスはメモリに比べるとかなりの低速なわけですから。

まず第一に、極力ハードディスクへのアクセス回数を減らすべきです。
つまり実行速度を考慮した場合、ファイルへの出力はデータをメモリ上に構築した上、
まとめて一度に行うのが望ましく、読込む場合も、いったんメモリ上へデータを
展開してから、検索するなり編集するなりといった方が望ましいと言えるのです。

当然ですが、ファイルオープン・クローズ回数は、できる限り少なくするべきです。
とはいえ、アプリケーションを起動した時点でファイルをオープンしてしまい、
終了させるまでそのまま開きっぱなし、というのも非常にセンスが悪く思えます。
排他制御が効きっぱなしになりますし、予期せぬ事態が起こらないとも限りません。

さて今回は例として、ファイル出力の方法について8パターンの比較を行いました。
これは私の製作するwindowsアプリケーションの評価のために行ったもので、
動的メモリ確保にnew()のみを使用していたり、読者様の指向とは異なる点も
あるとは思いますが、ファイル入出力方法で外せないポイントという点では、
本質とはあまり関係のない問題と考えますので、ご了承ください。

結果から得られる情報

int型データ10万個程度の試行では・・・・

  1. 一般的に、ファイルへの書込みはまとめて実行した方が速度が出ると言える。
  2. ファイルオープン・クローズだけならWin32APIとCライブラリで大きな差はないが、
    WriteFile?()に比べ、Cライブラリのfwrite()は実行速度的に優れると思われ、(課題参照)
    windowsアプリでもCライブラリ関数を用いることは、移植性も含めて大きな意味がある。(特記事項i参照)
  3. 費用対効果を考えると、fwrite()関数を用いる場合、自ら大きなメモリ領域を確保してまで
    ファイル出力をまとめる必要性は少ないと言える。(特記事項ii参照)
  4. 動的確保でも静的配列でも、一度だけならほとんど実行時間に差はないため、
    動的メモリ確保の方が好ましいと考えられる。(特記事項iii参照)

特記事項

  1. fopen()を使ってファイル操作を行う場合、windows上ではファイルロックに難あり。
  2. fwrite()関数はstdio.h内で定義される大きさの内部バッファを用いており、チューニング可能である。
  3. 環境にもよるが、万単位で静的な配列を宣言するのは、連続したメモリ領域確保が難しくなるため危険である。

出力結果

100000 times open, write and close, using win32API.
it takes 277321 msec.

100000 times WriteFile().
it takes 299 msec.

one time WriteFile(), using new().
it takes 6 msec.

one time WriteFile(), using array.
it takes 5 msec.

100000 times open, write and close, using C library.
it takes 277634 msec.

100000 times fwrite().
it takes 13 msec.

one time fwrite(), using new().
it takes 6 msec.

one time fwrite(), using array.
it takes 6 msec.

fileTest.cpp

#include        <stdio.h>                                       //  標準入出力                                    //
#include        <time.h>                                        //  時間計測のため                                //
#include        <windows.h>                                     //  for win32API                                  //
                                                                //                                                //
#define         TEST_NUM        100000                          //  試行回数 *大き過ぎると配列確保が難しくなる*   //
                                                                //                                                //
/* File Test **************************************************//*  ファイル出力時間計測テスト                    */
int main( void )                                                //                                                //
{                                                               //                                                //
    clock_t start, end;                                         //  時間計測用                                    //
    HANDLE OUFO_API;                                            //  win32APIを使用したファイル操作用              //
    DWORD writtenSize;                                          //  書込みに成功したサイズの取得用                //
    FILE *OUFO_C;                                               //  Cライブラリを使用したファイル操作用           //
                                                                //                                                //
    int data = 0x7234ABCD;                                      //  とりあえず大きくて単純でない数字              //
    int cnt;                                                    //  ループカウンタ                                //
                                                                //                                                //
    int buf_arr[TEST_NUM];                                      //  単純な配列のバッファ                          //
    int *buf_new;                                               //  new関数を使って確保するバッファ               //
                                                                //                                                //
    LARGE_INTEGER li;                                           //  for SetFilePointerEx()                        //
    li.LowPart = 0;                                             //  LARGE_INTEGER初期化(ファイルポインタ移動なし) //
    li.HighPart = 0;                                            //  LARGE_INTEGER初期化(ファイルポインタ移動なし) //
                                                                //                                                //
    for( ; ; )                                                  //  無限ループ                                    //
    {                                                           //                                                //
/* API Open Write Close Each Time *****************************//*  ファイルオープン、書込み、クローズを毎回(API) */
        start = clock();                                        //  時間計測スタート                              //
        for( cnt = 0; TEST_NUM > cnt; cnt++ )                   //  試行回数分繰返す                              //
        {                                                       //                                                //
            OUFO_API = CreateFile( ".\\a.txt",                  //  ファイルオープン                              //
                                   GENERIC_WRITE,               //  操作モード指定                                //
                                   FILE_SHARE_READ,             //  排他制御                                      //
                                   NULL,                        //  セキュリティ関連構造体のポインタ              //
                                   OPEN_ALWAYS,                 //  オープンモード指定                            //
                                   FILE_ATTRIBUTE_NORMAL,       //  属性指定                                      //
                                   NULL );                      //  テンプレートファイル                          //
            if( INVALID_HANDLE_VALUE == OUFO_API )              //  ファイルオープンのチェック                    //
            {                                                   //  ファイルオープンに失敗した場合                //
                printf( "ERR: CreateFile()\n" );                //  エラー表示                                    //
                break;                                          //  とりあえず直近の繰返しは抜ける                //
            }                                                   //                                                //
                                                                //                                                //
            SetFilePointerEx( OUFO_API, li, NULL, FILE_END );   //  シーク位置をファイル末尾に移動                //
            WriteFile( OUFO_API, &data, sizeof(int), &writtenSize, NULL ); //  データをファイルに書込む           //
                                                                //                                                //
            data--;                                             //  ちょっとしたデータの編集                      //
                                                                //                                                //
            CloseHandle( OUFO_API );                            //  ファイルクローズ                              //
        }                                                       //                                                //
        end = clock();                                          //  時間計測ストップ                              //
        printf( "%d times open, write and close, using win32API.\n", TEST_NUM ); //  画面表示                     //
        printf( "it takes %d msec.\n\n", end - start );                          //  画面表示                     //
//*API Open Write Close Each Time *****************************//*  ファイルオープン、書込み、クローズを毎回(API) */
/* API Write Each Time ****************************************//*  ファイル書込みを毎回行う(API)                 */
        data = 0x7234ABCD;                                      //  データを元に戻しておく *同じ条件で比較*       //
        start = clock();                                        //  時間計測スタート                              //
                                                                //                                                //
        OUFO_API = CreateFile( ".\\b.txt", GENERIC_WRITE, FILE_SHARE_READ,       //  ファイルオープン             //
                               NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); //                               //
        if( INVALID_HANDLE_VALUE == OUFO_API )                  //  ファイルオープンのチェック                    //
        {                                                       //  ファイルオープンに失敗した場合                //
            printf( "ERR: CreateFile()\n" );                    //  エラー表示                                    //
        }                                                       //                                                //
                                                                //                                                //
        for( cnt = 0; TEST_NUM > cnt; cnt++ )                   //  試行回数分繰返す                              //
        {                                                       //                                                //
            WriteFile( OUFO_API, &data, sizeof(int), &writtenSize, NULL ); //  データをファイルに書込む           //
            data--;                                             //  ちょっとしたデータの編集                      //
        }                                                       //                                                //
                                                                //                                                //
        CloseHandle( OUFO_API );                                //  ファイルクローズ                              //
                                                                //                                                //
        end = clock();                                          //  時間計測ストップ                              //
        printf( "%d times WriteFile().\n", TEST_NUM );          //  画面表示                                      //
        printf( "it takes %d msec.\n\n", end - start );         //  画面表示                                      //
//*API Write Each Time ****************************************//*  ファイル書込みを毎回行う(API)                 */
/* API Write in One using Dynamically Allocated Array *********//*  ファイル書込みを一回で(API new()使用)         */
        data = 0x7234ABCD;                                      //  書込み用データを戻しておく                    //
        start = clock();                                        //  時間計測スタート                              //
                                                                //                                                //
        buf_new = new int[TEST_NUM];                            //  new()により動的確保                           //
                                                                //                                                //
        OUFO_API = CreateFile( ".\\c.txt", GENERIC_WRITE, FILE_SHARE_READ,       //  ファイルオープン             //
                               NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); //                               //
        if( INVALID_HANDLE_VALUE == OUFO_API )                  //  ファイルオープンのチェック                    //
        {                                                       //  ファイルオープンに失敗した場合                //
            printf( "ERR: CreateFile()\n" );                    //  エラー表示                                    //
        }                                                       //                                                //
                                                                //                                                //
        for( cnt = 0; TEST_NUM > cnt; cnt++ )                   //  試行回数分繰返す                              //
        {                                                       //                                                //
            buf_new[cnt] = data--;                              //  動的に確保した領域にデータを格納していく      //
        }                                                       //                                                //
                                                                //                                                //
        WriteFile( OUFO_API, buf_new, sizeof(int) * TEST_NUM, &writtenSize, NULL ); // メモリ上データの一括書込み //
                                                                //                                                //
        delete [] buf_new;                                      //  動的に確保した領域の解放                      //
                                                                //                                                //
        CloseHandle( OUFO_API );                                //  ファイルクローズ                              //
                                                                //                                                //
        end = clock();                                          //  時間計測ストップ                              //
        printf( "one time WriteFile(), using new().\n" );       //  画面表示                                      //
        printf( "it takes %d msec.\n\n", end - start );         //  画面表示                                      //
//*API Write in One using Dynamically Allocated Array *********//*  ファイル書込みを一回で(API new()使用)         */
/* API Write in One using Static Array ************************//*  ファイル書込みを一回で(API 静的配列)          */
        data = 0x7234ABCD;                                      //  書込み用データを戻しておく                    //
        start = clock();                                        //  時間計測スタート                              //
                                                                //                                                //
        OUFO_API = CreateFile( ".\\d.txt", GENERIC_WRITE, FILE_SHARE_READ,       //  ファイルオープン             //
                               NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); //                               //
        if( INVALID_HANDLE_VALUE == OUFO_API )                  //  ファイルオープンのチェック                    //
        {                                                       //  ファイルオープンに失敗した場合                //
            printf( "ERR: CreateFile()\n" );                    //  エラー表示                                    //
        }                                                       //                                                //
                                                                //                                                //
        for( cnt = 0; TEST_NUM > cnt; cnt++ )                   //  試行回数分繰返す                              //
        {                                                       //                                                //
            buf_arr[cnt] = data--;                              //  静的に確保した領域にデータを格納していく      //
        }                                                       //                                                //
                                                                //                                                //
        WriteFile( OUFO_API, buf_arr, sizeof(buf_arr), &writtenSize, NULL ); //  メモリ上データの一括書込み       //
                                                                //                                                //
        CloseHandle( OUFO_API );                                //  ファイルクローズ                              //
                                                                //                                                //
        end = clock();                                          //  時間計測ストップ                              //
        printf( "one time WriteFile(), using array.\n" );       //  画面表示                                      //
        printf( "it takes %d msec.\n\n", end - start );         //  画面表示                                      //
//*API Write in One using Static Array ************************//*  ファイル書込みを一回で(API 静的配列)          */
/* C Open Write Close Each Time *******************************//*  ファイルオープン、書込み、クローズを毎回(C)   */
        data = 0x7234ABCD;                                      //  書込み用データを戻しておく                    //
        start = clock();                                        //  時間計測スタート                              //
                                                                //                                                //
        for( cnt = 0; TEST_NUM > cnt; cnt++ )                   //  試行回数分繰返す                              //
        {                                                       //                                                //
            OUFO_C = fopen( "e.txt", "ab" );                    //  追記バイナリでファイルオープン                //
            if( NULL == OUFO_C )                                //  ファイルオープンのチェック                    //
            {                                                   //  ファイルオープンに失敗した場合                //
                printf( "ERR: fopen()\n" );                     //  エラー表示                                    //
                break;                                          //  とりあえず直近の繰返しは抜ける                //
            }                                                   //                                                //
                                                                //                                                //
            fwrite( &data, sizeof(int), 1, OUFO_C );            //  データのファイルへの書込み                    //
            data--;                                             //  ちょっとしたデータの編集                      //
                                                                //                                                //
            fclose( OUFO_C );                                   //  ファイルクローズ                              //
        }                                                       //                                                //
                                                                //                                                //
        end = clock();                                          //  時間計測ストップ                              //
        printf( "%d times open, write and close, using C library.\n", TEST_NUM ); //  画面表示                    //
        printf( "it takes %d msec.\n\n", end - start );                           //  画面表示                    //
//*C Open Write Close Each Time *******************************//*  ファイルオープン、書込み、クローズを毎回(C)   */
/* C Write Each Time ******************************************//*  ファイル書込みを毎回行う(C)                   */
        data = 0x7234ABCD;                                      //  書込み用データを戻しておく                    //
        start = clock();                                        //  時間計測スタート                              //
                                                                //                                                //
        OUFO_C = fopen( "f.txt", "ab" );                        //  追記バイナリでファイルオープン                //
        if( NULL == OUFO_C )                                    //  ファイルオープンのチェック                    //
        {                                                       //  ファイルオープンに失敗した場合                //
            printf( "ERR: fopen()\n" );                         //  エラー表示                                    //
        }                                                       //                                                //
                                                                //                                                //
        for( cnt = 0; TEST_NUM > cnt; cnt++ )                   //  試行回数分繰返す                              //
        {                                                       //                                                //
            fwrite( &data, sizeof(int), 1, OUFO_C );            //  データのファイルへの書込み                    //
            data--;                                             //  ちょっとしたデータの編集                      //
        }                                                       //                                                //
        fclose( OUFO_C );                                       //  ファイルクローズ                              //
        end = clock();                                          //  時間計測ストップ                              //
        printf( "%d times fwrite().\n", TEST_NUM );             //  画面表示                                     //
        printf( "it takes %d msec.\n\n", end - start );          //  画面表示                                     //
//*C Write Each Time ******************************************//*  ファイル書込みを毎回行う(C)                   */
/* C Write in One using Dynamically Allocated Array ***********//*  ファイル書込みを一回で(C new()使用)           */
        data = 0x7234ABCD;                                      //  書込み用データを戻しておく                    //
        start = clock();                                        //  時間計測スタート                              //
                                                                //                                                //
        buf_new = new int[TEST_NUM];                            //  newにより動的確保                             //
                                                                //                                                //
        OUFO_C = fopen( "g.txt", "ab" );                        //  追記バイナリでファイルオープン                //
        if( NULL == OUFO_C )                                    //  ファイルオープンのチェック                    //
        {                                                       //  ファイルオープンに失敗した場合                //
            printf( "ERR: fopen()\n" );                         //  エラー表示                                    //
        }                                                       //                                                //
                                                                //                                                //
        for( cnt = 0; TEST_NUM > cnt; cnt++ )                   //  試行回数分繰返す                              //
        {                                                       //                                                //
            buf_new[cnt] = data--;                              //  動的に確保した領域にデータを格納していく      //
        }                                                       //                                                //
                                                                //                                                //
        fwrite( buf_new, sizeof(int), TEST_NUM, OUFO_C );       //  メモリ上データの一括書込み                    //
                                                                //                                                //
        fclose( OUFO_C );                                       //  ファイルクローズ                              //
                                                                //                                                //
        delete [] buf_new;                                      //  動的に確保した領域の解放                      //
                                                                //                                                //
        end = clock();                                          //  時間計測ストップ                              //
        printf( "one time fwrite(), using new().\n" );          //  画面表示                                      //
        printf( "it takes %d msec.\n\n", end - start );         //  画面表示                                      //
//*C Write in One using Dynamically Allocated Array ***********//*  ファイル書込みを一回で(C new()使用)           */
/* C Write in One using Static Array **************************//*  ファイル書込みを一回で(C 静的配列)            */
        data = 0x7234ABCD;                                      //  書込み用データを戻しておく                    //
        start = clock();                                        //  時間計測スタート                              //
                                                                //                                                //
        OUFO_C = fopen( "h.txt", "ab" );                        //  追記バイナリでファイルオープン                //
        if( NULL == OUFO_C )                                    //  ファイルオープンのチェック                    //
        {                                                       //  ファイルオープンに失敗した場合                //
            printf( "ERR: fopen()\n" );                         //  エラー表示                                    //
        }                                                       //                                                //
                                                                //                                                //
        for( cnt = 0; TEST_NUM > cnt; cnt++ )                   //  試行回数分繰返す                              //
        {                                                       //                                                //
            buf_arr[cnt] = data--;                              //  静的に確保した領域にデータを格納していく      //
        }                                                       //                                                //
                                                                //                                                //
        fwrite( buf_arr, sizeof(int), TEST_NUM, OUFO_C );       //  メモリ上データの一括書込み                    //
                                                                //                                                //
        fclose( OUFO_C );                                       //  ファイルクローズ                              //
                                                                //                                                //
        end = clock();                                          //  時間計測ストップ                              //
        printf( "one time fwrite(), using array.\n" );          //  画面表示                                      //
        printf( "it takes %d msec.\n\n", end - start );         //  画面表示                                      //
//*C Write in One using Static Array **************************//*  ファイル書込みを一回で(C 静的配列)            */
        break;                                                  //                                                //
    }                                                           //                                                //
                                                                //                                                //
    return( 0 );                                                //  処理終了                                      //
}                                                               //                                                //
//*File Test **************************************************//*  ファイル出力時間計測テスト                    */

修正履歴


課題

  1. 試行回数10万回というのは大きすぎて危険か?
    → C99の場合、65535バイトまでしか保証されないらしい。
  2. fwrite()関数がなぜWriteFile?()より速いのか、何か見落としている点はないか?
    CreateFile?()のオプション指定の仕方など

暇があればさらに調べてみます。

備考

テスト中に他の作業するとテスト結果に影響でるよ!

参考リンク

fwriteのバッファサイズ変更


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2010-02-08 (月) 02:08:09 (5192d)