SDK32:NT の非同期ディスク I/O は同期のように見える

文書翻訳 文書翻訳
文書番号: 156932 - 対象製品
この記事は、以前は次の ID で公開されていました: JP156932
すべて展開する | すべて折りたたむ

目次

概要

Windows NT のファイル入出力は、同期型または非同期型で行うことができます。デフォルトの入出力動作は同期型で、入出力関数が呼び出されて入出力が完了した時に復帰します。一方の非同期型入出力は、入出力関数の呼び出し元にすぐに実行を戻しますが、その入出力が将来完了するとは限りません。オペレーティング システムは、入出力が完了する時に呼び出し側に通知します。これに対して、呼び出し側は、オペレーティング システムのサービスを利用することによって、発行済の入出力動作の状態を判断することができます。

非同期型入出力の利点は、呼び出し側が他の仕事をしたり、入出力動作が完了するまで別のリクエストを発行できることにあります。オーバーラップト入出力という言葉は、非同期型入出力によく使われ、同期型入出力ではノンオーバーラップト入出力と言います。この文書は、CreateFile、ReadFile、WriteFile のようなファイル入出力の API を熟知したユーザーを対象にしています。

非同期型入出力動作はよく同期型入出力としても作用します。後述しますが、ある条件において、入出力動作を同期的に完了させます。入出力が完了するまで入出力関数から戻らないので、呼び出し側はバックグラウンドで仕事をする時間はありません。

同期型と非同期型の入出力に関係するいくつかの API があります。ReadFile と WriteFile が例としてこの文書でも使われていますが、ReadFileEx と WriteFileEx でも使えます。この文書では特にディスク入出力を検討していますが、その原理の多くは、シリアル入出力やネットワーク入出力のような (この文書では割愛いたしますが)、他の入出力の種類に適用することができます。Windows 95 ではディスク装置に対する非同期の入出力をサポートしていませんが、他の種類の入出力デバイスで非同期型動作をサポートしている点に注意してください。Windows 95 はディスク装置に対して非同期の入出力をサポートしていませんので、これに関してはこの文書で扱う対象とはなりません。

詳細

非同期型入出力の設定

ファイルをオープンする時に、CreateFile で FILE_FLAG_OVERLAPPED フラグを指定しなければなりません。このフラグはそのファイルに対する入出力動作を非同期に行うことを可能にします。以下に例を示します:
   HANDLE hFile;

   hFile = CreateFile(szFileName,
                      GENERIC_READ,
                      0,
                      NULL,
                      OPEN_EXISTING,
                      FILE_FLAG_NORMAL | FILE_FLAG_OVERLAPPED,
                      NULL);

   if (hFile == INVALID_HANDLE_VALUE)
      ErrorOpeningFile();
システムは必要ならば入出力動作を同期型にすることがあるので、非同期型入出力のコーディングには注意してください。プログラムは同期型か非同期型のどちらかで完了する入出力動作を正しく扱うように記述します。このことを考慮したコーディング例を示します。

プログラムは、非同期型操作を完了するために待機している間に、追加の操作をキューイングしたり、バックグラウンド処理をしたりなどできることがたくさんあります。たとえば、以下のコードは、読み込み操作のオーバーラップトとノンオーバーラップトを正しく取り扱っています。完了するために発行済の入出力を待機する以外のことは何もしていません:
   if (!ReadFile(hFile,
                 pDataBuf,
                 dwSizeOfBuffer,
                 &NumberOfBytesRead,
                 &osReadOperation )
   {
      if (GetLastError() != ERROR_IO_PENDING)
      {
         // ファイル読み込み中に他のエラーが発生した
         ErrorReadingFile();
         ExitProcess(0);
      }
      else
         // 処理はキューイングされて、
         // そのうち完了する
         fOverlapped = TRUE;
   }
   else
      // 処理はすぐに完了した
      fOverlapped = FALSE;

   if (fOverlapped)
   {
      // 必要なバックグラウンド処理を続け、
      // 動作が完了するまで待機する
      if (GetOverlappedResult( hFile,
                               &osReadOperation,
                               &NumberOfBytesTransferred,
                               TRUE))
         ReadHasCompleted(NumberOfBytesTransferred);
      else
         // 処理は完了したが、失敗した
         ErrorReadingFile();
   }
   else
      ReadHasCompleted(NumberOfBytesRead);
ReadFile で &NumberOfBytesRead を渡すことと、GetOverlappedResult で &NumberOfBytesTransferred を渡すことの違いに注意してください。動作が非同期ならば GetOverlappedResult は、一度完了した動作の実際の転送バイト数を判断するために使用します。ReadFile で渡される &NumberOfBytesRead は意味がありません。しかし一方では、動作が即座に完了した場合、ReadFile で渡された &NumberOfBytesRead は読み込まれたバイト数として有効です。このような場合では、ReadFile に渡された OVERLAPPED 構造体は無視すべきです。また、GetOverlappedResult や WaitForSingleObject を使う必要もありません。

非同期操作でのもう 1 つの注意は、OVERLAPPED 構造体をその保留された操作が完了するまで再使用してはいけないということです。言い換えると、3 つの発行済みの入出力動作を持つ場合、3 つの OVERLAPPED 構造体を使っていなければなりません。OVERLAPPED 構造体の再使用は、入出力動作に予測できない結果をもたらす原因となり、データ改悪の原因となるかもしれません。それに加えて、OVERLAPPED 構造体が、最初に使われるか、先の操作の完了後に再使用される前に、それを正しく初期化すれば、残ったデータが新しい操作に影響することはないでしょう。

同じような制限が、その操作の中でデータバッファを扱う時にも適用されます。データバッファは、その対応する入出力操作が完了するまで、読み書きしてはいけません:バッファの読み書きは、エラーと不正データの原因となるかもしれません。

非同期型入出力はまだ同期的に見える

上に示した命令に従った場合、入出力動作は、まだ、発行された順にすべて同期的に完了する傾向があり、実際に、どの ReadFile 操作も FALSE で返らず、GetLastError() は ERROR_IO_PENDING を返さないし、バックグラウンドで動作する時間を与えません。なぜこのようなことが起こるのでしょうか?

たとえ非同期操作用にコーディングしたとしても、入出力動作が同期的になる理由はたくさんあります:

要約

非同期操作に対する障害の 1 つとして、NTFS 圧縮があります。ファイル システム ドライバは、圧縮ファイルを非同期的ににアクセスしません;その代わりに、すべての操作は、同期的に行います。これは、COMPRESS や PKZIP のようなユーティリティで圧縮されているファイルに対しては適用されません。

ファイルの拡張

入出力操作が同期的に完了するもう 1 つの理由は、それ自身の操作にあります。Windows NT では、ファイルのサイズを拡張する書き込み操作は同期的になります。

キャッシュ

ほとんどの入出力ドライバ (ディスクや通信など) は、入出力リクエストが「直ちに」完了できる場合、その操作を完了し、ReadFile または WriteFile 関数に TRUE を返すような、特別処理のコードを持っています。すべての方法で、これらの種類の操作は同期的に見えます。ディスク デバイスにおいて、入出力リクエストが「直ちに」完了できるのは、データがメモリにキャッシュされている時が一般的です。

データがキャッシュにない

データがキャッシュに存在しないような場合、キャッシュ方式はそれに逆らって働きます。Windows NT のキャッシュは、ファイル マッピングを使って内部的に実行されています。Windows NT のメモリ マネージャは、キャッシュ マネージャで使われたファイル マッピングを管理するための、非同期のページ フォルト メカニズムを提供していません。しかし、キャッシュ マネージャは、要求されたページがメモリにあるかどうかを知らせることができるので、非同期のキャッシュされた読み込みを発行して、そのページがメモリに存在しないような場合、ファイル システム ドライバは、そのスレッドはブロックされたくない、そして、リクエストはワーカ スレッドの限られたプールによって扱われるだろうと仮定します。読み込みがまだ途中である ReadFile の呼び出しの後に、そのプログラムに制御を返します。少ない数のリクエストに対してはうまく働きますが、ワーカ スレッドのプールは制限 (現在 16MB システムでは 3 つ) がありますので、与えられた時間に、ディスク ドライバに対して 2、3 のリクエストがキューに入るだけでしょう。キャッシュに存在しないデータに対するたくさんの入出力操作を発行した場合、キャッシュ マネージャとメモリ マネージャはいっぱいになり、そのリクエストは同期的になります。

キャッシュ マネージャの作用は、シーケンシャル、あるいは、ランダムにファイルにアクセスするかどうかに関係なく影響しています。キャッシュの恩恵は、シーケンシャルにファイルをアクセスしている時に最も多くわかります。CreateFile の呼び出しに指定する FILE_FLAG_SEQUENTIAL_SCAN フラグは、この種のアクセスのためにキャッシュを最適化します。しかし、ランダムにファイルをアクセスする場合、キャッシュ マネージャに対して、ランダム アクセスのためにその作用を最適化するように命令するために、CreateFile には FILE_FLAG_RANDOM_ACCESS フラグを使ってください。

キャッシュを使わない

FILE_FLAG_NO_BUFFERING フラグは、非同期型操作に対するファイル システムの動作に大きな影響を持っています。これは、入出力リクエストが実際に非同期になることを保証する最高の方法です。これはファイル システムにまったくキャッシュ メカニズムを利用しないことを命令します。このフラグを使用する上で、データ バッファの位置合わせと、デバイスのセクタ サイズを合わせなければならないという、いくつかの制限があることに注意してください。このフラグの使用法に関する詳細は、CreateFile 関数のドキュメントを見てください。

サンプルコード

以下のファイルが Microsoft ソフトウェア ライブラリ (MSL) からダウンロード可能です:
Asynczip.exe (サイズ: 43,594 bytes)
Microsoft ソフトウェア ライブラリからのダウンロードについての情報は、以下の文章番号の Knowledge Base をご覧ください。
119591 オンライン サービスからマイクロソフトのサポート ファイルを入手する方法


この文書に関連したサンプルコードは、フラグと API の使用方法を示します。コードは、Windows NT のコンソール アプリケーションとして動作します。以下のコマンドラインで機能を制御します:
  Asynchio
  使用方法: asynchio [オプション]

オプションについて:
   /fFilePattern  入出力に使用するファイル
   /s             同期型動作を指定
   /n             バッファを使用しないことを指定
   /r             FILE_FLAG_RANDOM_ACCESS を使用
   /l             FILE_FLAG_SEQUENTIAL_SCAN を使用
   /o###          ### 操作を発行
   /e             最初に全体を読み込み、後で細かく読み込む
   /?             この使用方法を表示
例: asynchio /f*.bmp /n

このプログラムはデフォルトで非同期、バッファの使用、500 回の入出力操作を行います。

実際のテスト結果

以下は、サンプルコードのいくつかのテスト結果です。試行回数はここでは重要ではなく、マシンによって変化するので、お互いに比較した数値の関係は、性能上におけるフラグの一般的な影響を明らかにしています。きっと同様の結果を見ることでしょう:
非同期、入出力バッファ無し:  asynchio /f*.dat /n

   Operations completed out of the order in which they were requested.
   500 requests queued in 0.224264 seconds.
   500 requests completed in 4.982481 seconds.
このテストは、上記のプログラムが素早く 500 回の入出力リクエストを発行し、別の仕事や別のリクエストを発行するための、重要な時間を与えられたことを示しています。
同期、入出力バッファ無し: asynchio /f*.dat /s /n

   Operations completed in the order issued.
   500 requests queued and completed in 4.495806 seconds.
このテストは、前のテストが同じリクエストを出すために、0.224264 秒を費やすだけだったのに対し、上記のプログラムがその操作を完了するために、4.495880 秒間を ReadFile の呼び出しに費やしたことを示しています。2 番めのテストでは、プログラムがバックグラウンドの仕事をするのための「余分な」時間はありませんでした。
非同期、入出力バッファ有り: asynchio /f*.dat

   Operations completed in the order issued.
   500 requests issued and completed in 0.251670 seconds.
このテストは、キャッシュの同期型の性質を示しています。すべての読み込みが発行されて、0.251670 秒で完了しました。言い換えると、非同期リクエストは同期的に完了しました。このテストも、データがキャッシュにある時に、キャッシュ マネージャの高いパフォーマンスを示しています。
同期、入出力バッファ有り: asynchio /f*.dat /s

   Operations completed in the order issued.
   500 requests and completed in 0.217011 seconds.
このテストは、上記と同じ結果を示しています。キャッシュからの同期型読み込みは、キャッシュからの非同期型読み込みより、わずかに速く完了していることに注意してください。このテストも、データがキャッシュにある時に、キャッシュ マネージャの高いパフォーマンスを示しています。

結論

最良の方法の決定は、プログラムが行うタイプ、サイズ、操作の数に完全に依存するため、あなたに任せます。

CreateFile に特別なフラグを指定しない場合のデフォルトのファイル アクセスは、同期型でキャッシュされた操作になります。ファイル システム ドライバが非同期の先読みと、変更されたデータの非同期の遅延書き込みを行うので、このモードにおいて、いくつかの自動的な非同期動作を行う点に注意してください。このことで、アプリケーションの入出力を非同期的にはしないですが、単純なアプリケーションの大多数にとって理想的なケースです。
もし、アプリケーションが単純ではない場合、最良の方法を見つけるために、上記に示したテストのように、いくつかの分析とパフォーマンスを監視する必要があるかもしれません。ReadFile または WriteFile API に費やした時間の分析と、完了するために実際の入出力操作にどのくらいの時間を費やしたかを比較することは非常に役に立ちます。その時間の大部分が実際に入出力の発行に費やされるならば、その入出力は同期的に完了しています。しかし、発行している入出力リクエストに費やされる時間が、完了する入出力操作のために費やす時間と比較して相対的に小さい場合、その操作は非同期的に扱われています。上記のサンプルコードでは、内部分析を行うために、QueryPerformanceCounter API を使用しています。

パフォーマンスの監視は、そのプログラムがどれくらい能率的にディスクとキャッシュを使用しているかを判断するのに役に立ちます。Cache オブジェクト用のパフォーマンス カウンタのどれかを追跡すれば、キャッシュ マネージャのパフォーマンスがわかります。Physical Disk か Logical Disk オブジェクト用のパフォーマンス カウンタを追跡すれば、ディスク システムのパフォーマンスがわかります。

パフォーマンスを監視するいくつかのユーティリティがあります:PerfMon と DiskPerf は、とても役に立ちます。diskperf -y コマンドで、システムに対して、ディスク システムのパフォーマンス データを集めることを有効にするので、これを最初に行います。このコマンドを出した後で、データ収集を始めるためにシステムを再起動する必要があります。

参照

これらのユーティリティとパフォーマンスのモニタについては、Windows NT リソースキットのドキュメントにある「Windows NT の最適化」を参照してください。

関連情報

この資料は米国 Microsoft Corporation から提供されている Knowledge Base の Article ID 156932 (最終更新日 1999-03-25) を基に作成したものです。

プロパティ

文書番号: 156932 - 最終更新日: 2004年9月8日 - リビジョン: 1.5
この資料は以下の製品について記述したものです。
  • Microsoft Win32 Application Programming Interface?を以下の環境でお使いの場合
    • Microsoft Windows NT 3.51 Service Pack 5
    • Microsoft Windows NT 4.0
キーワード:?
kbdownload kbtshoot kbsample asynchronous getoverlappedresult overlapped KB156932
"Microsoft Knowledge Baseに含まれている情報は、いかなる保証もない現状ベースで提供されるものです。Microsoft Corporation及びその関連会社は、市場性および特定の目的への適合性を含めて、明示的にも黙示的にも、一切の保証をいたしません。さらに、Microsoft Corporation及びその関連会社は、本文書に含まれている情報の使用及び使用結果につき、正確性、真実性等、いかなる表明・保証も行ないません。Microsoft Corporation、その関連会社及びこれらの権限ある代理人による口頭または書面による一切の情報提供またはアドバイスは、保証を意味するものではなく、かつ上記免責条項の範囲を狭めるものではありません。Microsoft Corporation、その関連会社 及びこれらの者の供給者は、直接的、間接的、偶発的、結果的損害、逸失利益、懲罰的損害、または特別損害を含む全ての損害に対して、状況のいかんを問わず一切責任を負いません。(Microsoft Corporation、その関連会社 またはこれらの者の供給者がかかる損害の発生可能性を了知している場合を含みます。) 結果的損害または偶発的損害に対する責任の免除または制限を認めていない地域においては、上記制限が適用されない場合があります。なお、本文書においては、文書の体裁上の都合により製品名の表記において商標登録表示、その他の商標表示を省略している場合がありますので、予めご了解ください。"

フィードバック

 

Contact us for more help

Contact us for more help
Connect with Answer Desk for expert help.
Get more support from smallbusiness.support.microsoft.com