.Net の非同期ファイル IO
-
01-07-2019 - |
質問
コンパイラー、オプティマイザー、インデックス技術について詳しく学ぶために、C# でおもちゃのデータベースを構築しています。
ページをバッファー プールに取り込むための (少なくとも読み取り) リクエスト間の並列処理を最大限に維持したいのですが、.NET でこれを実現する最善の方法について混乱しています。
以下にいくつかのオプションと、それぞれのオプションで遭遇した問題を示します。
使用
System.IO.FileStream
そしてそのBeginRead
方法ただし、ファイル内の位置は引数ではありません。
BeginRead
, のプロパティです。FileStream
(経由で設定Seek
メソッド)のため、一度に 1 つのリクエストしか発行できず、その間ストリームをロックする必要があります。(それとも私ですか?ドキュメントでは、ロックを次の間だけ保持した場合に何が起こるかが不明瞭です。Seek
そしてBeginRead
電話をかけたが、電話をかける前に放したEndRead
. 。誰か知っていますか?)これを行う方法は知っていますが、それが最善の方法であるかどうかはわかりません。を中心とした別の方法があるようです
System.Threading.Overlapped
構造体と P\Invoke をReadFileEx
kernel32.dllの関数。残念ながら、特にマネージ言語ではサンプルが不足しています。このルートには (もしそれが機能するとしても) 明らかに次のことも含まれています。
ThreadPool.BindHandle
メソッドとスレッド プール内の IO 完了スレッド。これが Windows でこのシナリオに対処する公認の方法であるような印象を受けますが、私にはそれが理解できず、初心者に役立つドキュメントへの入り口も見つかりません。他に何か?
コメントの中で、jacob は新しいものを作成することを提案しています。
FileStream
飛行中の読み取りごとに。ファイル全体をメモリに読み取ります。
データベースが小さい場合、これは機能します。コードベースは小さく、他にも非効率な点がたくさんありますが、データベース自体はそうではありません。また、大規模なデータベースを処理するために必要なすべての簿記を確実に実行したいと考えています (これが複雑さの大きな部分であることがわかります:ページング、外部ソートなど)、あまりにも簡単に誤って不正行為をしてしまうのではないかと心配しています。
編集
解決策 1 を疑う理由の明確化:BeginRead から EndRead まで単一のロックを保持するということは、別の読み取りが進行中であるという理由だけで読み取りを開始しようとするユーザーをブロックする必要があることを意味します。新しい読み取りを開始するスレッドは、結果が利用可能になる前に (一般に) 追加の作業を実行できる可能性があるため、これは間違っていると感じます。(実際、これを書いているだけで新しい解決策を思いついたので、新しい答えとして投稿しました。)
解決
私たちが行ったのは、C++/CLI で I/O 完了ポート、ReadFile、および GetQueuedCompletion ステータスの周囲に小さなレイヤーを作成し、操作が完了したときに C# にコールバックすることでした。ファイル (またはソケット) からの読み取りに使用されるバッファーをより詳細に制御できるようにするために、BeginRead および C# 非同期操作パターンではなくこのルートを選択しました。これは、読み取りごとにヒープ上に新しい byte[] を割り当てる純粋なマネージド アプローチに比べて、かなり大きなパフォーマンスの向上でした。
さらに、インターウェブ上には、IO Completion ポートを使用する完全な C++ サンプルが多数あります。
他のヒント
なぜオプション 1 がうまくいかないのかわかりません。2 つの異なるスレッドで同じ FileStream を同時に使用することはできないことに注意してください。使用すると、間違いなく問題が発生します。BeginRead/EndRead は、高コストになる可能性がある IO 操作が行われている間もコードの実行を継続できるようにすることを目的としており、ファイルに対するある種のマルチスレッド アクセスを有効にすることを目的としたものではありません。
したがって、seek してから beginread を実行することをお勧めします。
まずリソース (ファイル データなど) をメモリにロードしてから、それをスレッド間で共有したらどうなるでしょうか?小さいデータベースなので。- 対処しなければならない問題はそれほど多くありません。
アプローチ #1 を使用します。 しかし
リクエストが届いたら、ロック A を取得します。これを使用して、保留中の読み取りリクエストのキューを保護します。それをキューに追加し、新しい非同期結果を返します。これによりキューへの最初の追加が行われた場合は、戻る前にステップ 2 を呼び出します。戻る前にロック A を解除してください。
読み取りが完了したら (またはステップ 1 によって呼び出されて)、ロック A を取得します。これを使用して、キューからの読み取りリクエストのポップを保護します。ロックBを取ります。を保護するために使用します。
Seek
->BeginRead
->EndRead
順序。ロックBを解除します。この読み取り操作の手順 1 で作成された非同期結果を更新します。(読み込みが完了したので再度呼び出します。)
これにより、別の読み取りが進行中であるという理由だけで読み取りを開始するスレッドをブロックせず、ファイル ストリームの現在位置が混乱しないように読み取りをシーケンスするという問題が解決されます。