(ちょっと前置きが長くなってすみません)
後で実際の実行を高速化するために、大きなファイル全体 (>400MB) をバッファ キャッシュにプレフォールトするアプリケーションの開発中に、一度に 4MB を読み取ることが、一度に 1MB のチャンクのみを読み取る場合よりも顕著な利点があるかどうかをテストしました。驚くべきことに、実際には小さなリクエストの方が高速であることが判明しました。これは直感に反するように思えたので、より広範なテストを実行しました。
テストを実行する前に、バッファ キャッシュを消去しました (笑いのために、私もバッファ内のファイルで 1 回実行しました。バッファ キャッシュは、要求のサイズに関係なく 2GB/秒以上を提供しますが、驚くべきことに +/- 30% です)。ランダム分散)。
すべての読み取りは、同じターゲット バッファーを持つオーバーラップした ReadFile を使用しました (ハンドルは を使用して、または使用せずに開かれましFILE_FLAG_OVERLAPPED
た) FILE_FLAG_NO_BUFFERING
。使用されているハードディスクはやや古いものですが、完全に機能しており、NTFS のクラスター サイズは 8kB です。ディスクは最初の実行後に最適化されました (6 つのフラグメントと非フラグメント、ゼロの差)。より良い数値を得るために、私はより大きなファイルも使用しました.以下の数値は1GBを読み取るためのものです.
結果は本当に驚くべきものでした:
4MB x 256 : 5ms per request, completion 25.8s @ ~40 MB/s
1MB x 1024 : 11.7ms per request, completion 23.3s @ ~43 MB/s
32kB x 32768 : 12.6ms per request, completion 15.5s @ ~66 MB/s
16kB x 65536 : 12.8ms per request, completion 13.5s @ ~75 MB/s
したがって、これは、 2 クラスタの長さで 1 万件のリクエストを送信する方が、数百の大規模な連続した読み取りを送信するよりも実際には優れていることを示唆しています。送信時間 (ReadFile が返されるまでの時間) は、要求の数が増えるにつれて大幅に増加しますが、非同期完了時間はほぼ半分になります。
非同期読み取りが完了している間、カーネルの CPU 時間はすべてのケースで約 5 ~ 6% です (クアッドコアでは、実際には 20 ~ 30% と言うべきです)。これは CPU の驚くべき量です。忙しい待機時間も無視できます。2.6 GHz で 25 秒間 30% の CPU。これは、「何もしない」ためのかなりの数のサイクルです。
これをどのように説明できますか?ここにいる誰かが、Windows オーバーラップ IO の内部動作についてより深い洞察を持っているのではないでしょうか? それとも、ReadFile を使用して 1 メガバイトのデータを読み取ることができるという考えに、何か根本的な間違いがありますか?
特にリクエストがランダム アクセスの場合 (ランダム アクセスではない場合)、IO スケジューラがシークを最小限に抑えることで複数のリクエストを最適化できることがわかります。また、NCQ でいくつかの要求が与えられた場合に、ハードディスクが同様の最適化をどのように実行できるかを確認できます。
しかし、私たちは途方もなく小さな要求の途方もない数について話しているのです。
補足:明らかな勝者はメモリ マッピングです。私はメモリマッピングの大ファンなので、「当然のことながら」追加する傾向がありますが、この場合、「要求」がさらに小さくなり、OS が予測して実行する能力がさらに低下するため、実際には驚きます。 IO をスケジュールします。最初はメモリ マッピングをテストしませんでした。リモートでも競合できる可能性があると直感に反するように思えたからです。あなたの直感はこれで終わりです。
ビューを異なるオフセットで繰り返しマッピング/マッピング解除すると、実質的に時間がかかりません。16MB のビューを使用し、ページごとに 1 バイトを読み取る単純な for() ループですべてのページをフォールトすると、9.2 秒 @ ~111 MB/秒で完了します。CPU 使用率は常に 3% (1 コア) 未満です。同じコンピューター、同じディスク、すべて同じ。
また、Windows は一度に 8 ページをバッファ キャッシュにロードするように見えますが、実際には 1 ページしか作成されません。8 ページごとにフォールトすると、同じ速度で実行され、ディスクから同じ量のデータが読み込まれますが、「物理メモリ」と「システム キャッシュ」のメトリックが低くなり、ページ フォールトの 1/8 しか表示されません。その後の読み取りでは、ページが確実にバッファ キャッシュ内にあることが証明されます (遅延やディスク アクティビティはありません)。
(おそらく、 Memory-Mapped File is Faster on Huge Sequential Read と非常に、非常に遠い関係がありますか? )
もう少しわかりやすくするために、次のようにします。
アップデート:
を使用するFILE_FLAG_SEQUENTIAL_SCAN
と、128k の読み取りの「バランス」がとれているように見え、パフォーマンスが 100% 向上します。一方、これは 512k と 256k の読み取りに深刻な影響を与え (なぜだろうか?)、それ以外には実際の影響はありません。より小さいブロック サイズの MB/秒グラフは、間違いなくもう少し「均一」に見えますが、実行時間には違いはありません。
ブロックサイズが小さいほどパフォーマンスが向上するという説明も見つけたかもしれません。ご存じのとおり、非同期リクエストは、OS がリクエストを即座に処理できる場合、つまりバッファから (およびさまざまなバージョン固有の技術的制限により) リクエストを処理できる場合、同期的に実行される場合があります。
実際の非同期読み取りと「即時」の非同期読み取りを比較すると、 256kを超えると、Windows はすべての非同期要求を非同期的に実行することに気付きます。ブロックサイズが小さいほど、すぐに利用できない場合でも、より多くの要求が「すぐに」処理されます(つまり、ReadFile は単に同期的に実行されます)。明確なパターン(「最初の100リクエスト」や「1000以上のリクエスト」など)は分かりませんが、リクエストサイズと同期性には逆相関があるようです。ブロックサイズが 8k の場合、すべての非同期リクエストは同期的に処理されます。
バッファリングされた同期転送は、何らかの理由で非同期転送の 2 倍の速さです (理由はわかりません)。したがって、要求サイズが小さいほど、より多くの転送が同期的に行われるため、転送全体が高速になります。
メモリ マップド プレフォールトの場合、FILE_FLAG_SEQUENTIAL_SCAN によってパフォーマンス グラフの形状がわずかに異なります (少し後ろに移動した「ノッチ」があります) が、所要時間の合計はまったく同じです (これも驚くべきことですが、私にはわかりません)。それを助ける)。
更新 2:
バッファリングされていない IO により、1M、4M、および 512k リクエストのテストケースのパフォーマンス グラフは、最大値が 90 GB/s で、やや高くなり、より「スパイキー」になりますが、最小値も厳しく、1GB の全体的な実行時間は +/- 0.5 以内です。バッファリングされた実行の s (ただし、2558 を超える進行中のリクエストでは、ERROR_WORKING_SET_QUOTA が返されるため、バッファ サイズが小さいリクエストは大幅に高速に完了します)。発生するすべての IO は DMA 経由で実行されるため、測定された CPU 使用率はすべての非バッファリングのケースでゼロです。これは当然のことです。
もう 1 つの非常に興味深い観察結果FILE_FLAG_NO_BUFFERING
は、API の動作が大幅に変わることです。少なくともIO をキャンセルCancelIO
するという意味では機能しません。バッファリングされていない進行中のリクエストでは、すべてのリクエストが完了するまで単純にブロックされます。弁護士はおそらく、関数が戻ったときに実行中のリクエストが残っていないため、関数がその義務を怠ったことについて責任を負うことはできないと主張するでしょう。は多少異なります。バッファリングされ、オーバーラップされた IO を
使用すると、単純にロープが切断され、実行中のすべての操作がすぐに終了します。CancelIO
CancelIO
もう 1 つの面白い点は、すべての要求が完了するか失敗するまで、プロセスを強制終了できないことです。OSがそのアドレス空間にDMAを実行している場合、この種のことは理にかなっていますが、それでも驚くべき「機能」です.