9

fwrite は内部で WriteFile を呼び出すので、私は常に WriteFile が fwrite よりも効率的であると考えてきましたが、次のテスト コードは、fwrite が WriteFile よりも大幅に高速であることを示しています。

fwrite には 2 ミリ秒のコストがかかりますが、WriteFile には 27000(FILE_ATTRIBUTE_NORMAL) が必要です。両方とも書き込み呼び出しのたびにフラッシュされます。FILE_FLAG_WRITE_THROUGH を指定して WriteFile を呼び出し、FlushFileBuffers(wfile) 行にコメントを付けると、WriteFile が高速になり、コストは 800 になります。

では、fwrite が WriteFile を呼び出しているのは本当ですか? 何がそんなに大きな違いを生んでいるのでしょうか? fwrite は内部でどのように機能しますか? fwrite よりも効率的に API を使用してデータをファイルに書き込むにはどうすればよいですか? (unbufferd、同期)。

   #include <Windows.h>
   #include <stdio.h>
   #include <iostream>

   int main() {
     FILE* cfile = fopen("file1.txt", "w");
     HANDLE wfile = CreateFile("file2.txt", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 
           /*FILE_ATTRIBUTE_NORMAL*/FILE_FLAG_WRITE_THROUGH, NULL);
     DWORD written = 0;

     DWORD start_time, end_time;
     char * text = "test message ha ha ha ha";
     int size = strlen(text);
     int times = 999;

     start_time = timeGetTime();
     for(int i = 0; i < times; ++i) {
       fwrite(text, 1, size, cfile);
       fflush(cfile);
     }
     end_time = timeGetTime();
     std::cout << end_time - start_time << '\n';

     start_time = timeGetTime();
     for(int i = 0; i < times; ++i) {
         WriteFile(wfile, text, size, &written, NULL);
         //FlushFileBuffers(wfile);
     }
     end_time = timeGetTime();
     std::cout << end_time - start_time << std::endl;

     system("pause");
     return 0;
   }

更新: 回答ありがとうございます。回答は次のとおりです: VS directory\VS\crt\src\fflush.c を参照してください:

    //fflush.c
    int __cdecl _fflush_nolock (FILE *str) {
        //irrelevant codes
        if (str->_flag & _IOCOMMIT) {
                return (_commit(_fileno(str)) ? EOF : 0);
        }
        return 0;
    }

ここに _IOCOMMIT フラグがあります。それから ...\src\fdopen.c を参照してください。

    FILE * __cdecl _tfdopen (int filedes, const _TSCHAR *mode) {
      //irrelevant codes
        while(*++mode && whileflag)
          switch(*mode) {
      //...
              case _T('c'):
                if (cnflag)
                    whileflag = 0;
                else {
                    cnflag = 1;
                    fileflag |= _IOCOMMIT;
                }
               break;
     //...
    }

_tfopen は fopen によって内部的に呼び出されます。fopen のドキュメントを参照してください。

" モード: 'c'

関連付けられたファイル名のコミット フラグを有効にして、fflush または _flushall が呼び出された場合にファイル バッファの内容がディスクに直接書き込まれるようにします。

_commit 関数は最終的に FlushFileBuffers を呼び出します。

これらに加えて、少数のデータのみをファイルに書き込む場合(バッファサイズを超えない場合)、fflush なしで fwrite を実行すると、FlushFileBuffers を呼び出さなくても WriteFile の後にテキストが明らかに書き込まれないことがわかります。 、ファイルを開くと(プログラムがスリープ状態にある)、コンテンツが自動的にファイルに書き込まれます。これが、フラッシュについて混乱した理由の1つでした。この操作はOSによって行われる可能性があり、WriteFileはデータをシステムキャッシュにコピーし、そのファイル バッファは OS によって管理されるため、fflush() が実際のフラッシュなしで内部的にのみ WriteFile を呼び出すことは合理的です。システムは、ファイル ハンドルが閉じられたとき、またはこのファイルへの別の I/O アクセスが発生したときに、それらをフラッシュするタイミングを認識しています。そこで、ベンチマークを次のように変更しました。

      start_time = timeGetTime();
for(int i = 0; i < times; ++i) {
    fwrite(text, 1, size, cfile);
    fflush(cfile);
}
end_time = timeGetTime();
std::cout << end_time - start_time << '\n';

start_time = timeGetTime();
for(int i = 0; i < times; ++i) {
    WriteFile(wfile, text, size, &written, NULL);
}
end_time = timeGetTime();
std::cout << end_time - start_time << std::endl;

結果は times:99999 fwrite:217 WriteFile:171 です

結論として、API ファイルの書き込み操作を高速化するには、次のようにします。

  1. FlushFileBuffers を明示的に呼び出さないでください。システム キャッシュ内のデータは、必要に応じてディスクにフラッシュされます。

  2. API 呼び出しは単に memcpy よりも時間がかかるため、fwrite と同様に、WriteFile のバッファーを取得します。バッファーがいっぱいになったら、WriteFile を呼び出します。

4

2 に答える 2

17

Sysinternals のProcess Monitor (procmon)などのツールを使用すると、 への呼び出しが(またはへのフラグ)fflush()と同じことを行っていないことがわかります。FlushFileBuffers(wfile)FILE_FLAG_WRITE_THROUGHCreateFile()

fwrite()バッファがいっぱいになるまでデータをバッファに書き込みます。これにより、バッファ内のデータがWriteFile()呼び出されます。を呼び出すとfflush()、現在バッファーにあるデータが呼び出しに渡されるだけで、呼び出しは行われませWriteFile()ん。fflush()FlushFileBuffers()

1:21:32.9391534 AM  test.exe    6132    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 0, Length: 24
1:21:32.9392200 AM  test.exe    6132    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 24, Length: 24
1:21:32.9392340 AM  test.exe    6132    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 48, Length: 24
1:21:32.9392436 AM  test.exe    6132    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 72, Length: 24
1:21:32.9392526 AM  test.exe    6132    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 96, Length: 24
1:21:32.9392623 AM  test.exe    6132    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 120, Length: 24

比較のために、呼び出しのfwrite()ないループからのトレースの例を次に示します。fflush()

1:27:28.5675034 AM  test.exe    3140    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 0, Length: 1,024
1:27:28.5676098 AM  test.exe    3140    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 1,024, Length: 1,024
1:27:28.5676399 AM  test.exe    3140    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 2,048, Length: 1,024
1:27:28.5676651 AM  test.exe    3140    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 3,072, Length: 1,024

WriteFile()そして、これはループからのトレースのスニペットです(FILE_ATTRIBUTE_NORMALフラグと明示的な呼び出しを使用して、2 番目の 4KB 呼び出しとして表示されるのではなく、トレースに呼び出しが表示されるFlushFileBuffers()ため、トレースで何が起こっているかを簡単に確認できます)。FlushFileBuffers()WriteFile()

1:21:29.0068503 AM  test.exe    6132    WriteFile   C:\temp\file2.txt   SUCCESS Offset: 0, Length: 24, Priority: Normal
1:21:29.0069197 AM  test.exe    6132    FlushBuffersFile    C:\temp\file2.txt   SUCCESS 
1:21:29.0069517 AM  test.exe    6132    WriteFile   C:\temp\file2.txt   SUCCESS Offset: 0, Length: 4,096, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal
1:21:29.0087574 AM  test.exe    6132    WriteFile   C:\temp\file2.txt   SUCCESS Offset: 24, Length: 24
1:21:29.0087798 AM  test.exe    6132    FlushBuffersFile    C:\temp\file2.txt   SUCCESS 
1:21:29.0088087 AM  test.exe    6132    WriteFile   C:\temp\file2.txt   SUCCESS Offset: 0, Length: 4,096, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal
1:21:29.0102260 AM  test.exe    6132    WriteFile   C:\temp\file2.txt   SUCCESS Offset: 48, Length: 24
1:21:29.0102428 AM  test.exe    6132    FlushBuffersFile    C:\temp\file2.txt   SUCCESS 
1:21:29.0102701 AM  test.exe    6132    WriteFile   C:\temp\file2.txt   SUCCESS Offset: 0, Length: 4,096, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal
1:21:29.0113444 AM  test.exe    6132    WriteFile   C:\temp\file2.txt   SUCCESS Offset: 72, Length: 24
1:21:29.0113602 AM  test.exe    6132    FlushBuffersFile    C:\temp\file2.txt   SUCCESS 
1:21:29.0113848 AM  test.exe    6132    WriteFile   C:\temp\file2.txt   SUCCESS Offset: 0, Length: 4,096, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal

したがって、ベンチマークがループに深刻な不利を示している理由は、WriteFile()単純に、ループ内にない約 1000 の呼び出しがあるためFlushFileBuffers()ですfwrite()

于 2013-01-12T09:35:36.827 に答える
5

適切に設定されていれば、WriteFile() よりも効率的ですfwrite()WriteFile()発行しているIOリクエストを実行するときに使用する条件を微調整できます。

たとえば、中間のバッファリングされた IO サブシステムをバイパスし、あたかも中間の IO バッファであるかのようにデータ ポインタから直接プルすることができるため、重要な仲介者を排除できますただし、セットアップには多少制限があります。データ ポインターは、書き込み先のボリュームのセクター サイズに相当するバイト境界上に配置する必要があります。fwrite()うまくいけば明白な理由で、そのような機能は存在しません。Windows API 愛好家 (J. Richter と彼の兄弟の頃) はWriteFile()、Windows プログラムの IO パフォーマンスから最後の一滴まで絞り出すために、 のような使用法をいじるのが大好きです。

そして、なぜ人々がWriteFile()ラブチルドレンではないのか疑問に思っているなら、多くの人がラブチルドレンであることは保証できますが、移植可能なコードにまったく関心がない人はいません。あるもの (または、それほど気にしていないもの (時期尚早の最適化についてクヌースが言ったことは..?) は、 のような標準的な機能を選択しますfwrite()

の MSVCRT 実装と、それがどのように機能するかに本当に興味がある場合は、ソースをチェックしてください。fwrite()VC++ Standard 以上のすべてのバージョンに同梱されています (おそらく Express ではありません。確認したことはありません)。

于 2013-01-12T06:19:37.513 に答える