ディスクからデータをより効率的に読み取る方法(.Netがあると仮定)





こんにちは、Habr! しばらく前、私は質問に興味がありました:ディスクからデータを読み取る最も効率的な方法は何ですか(.Netがある場合)? 多数のファイルを読み取るタスクは、多くのプログラムで見られます。多くのプログラムは、最初から構成の読み取りを開始し、一部のロードモジュールを独自に読み込みます。



インターネットでは、このような比較は見つかりませんでした(特定の構成のチューニングを除く)。



結果はGithubで見ることができます: SSDHDD



読み取り方法とテストアルゴリズム



基本的な方法がいくつかあります。





SSDとHDDですべてをテストしました(最初のケースではXeon 24コアと16 GBのメモリとIntel SSDを搭載したコンピューターがあり、2つ目はCore i5、4 GBのRAMと5400 rpmのHDDを搭載したMac Mini MGEM2LL / Aでした)。 システムは、結果に応じて、比較的新しいシステムではなく、比較的新しいシステムで最も適切に動作する方法を理解できるようになっています。



プロジェクトはここで表示できます 。これは、1つのメインTestsHost実行可能ファイルと、シナリオ*という名前の一連のプロジェクトです。 各テストは次のとおりです。



  1. 正味時間を計算するexeファイルを実行します。



  2. 1秒に1回、プロセッサの負荷、RAMの消費、ディスクの負荷、およびその他の多数の派生パラメーターがチェックされます( パフォーマンスカウンターを使用)。



  3. 結果は記憶され、テストは数回繰り返されます。 最終結果は、最大値と最小値を除いた平均時間です。



テストの準備は複雑です。 したがって、開始する前に:



  1. ファイルのサイズと数を決定します(ディスクキャッシュの影響を抑えるために、合計ボリュームがRAMサイズよりも大きくなるように選択しました)。



  2. コンピューター上で特定のサイズのファイルを探しています(同時に、以下で説明するアクセスできないファイルといくつかの特別なフォルダーを無視します)。



  3. ファイルのセットに対してテストの1つを実行しますが、結果は無視します。 これはすべて、OSキャッシュをリセットし、以前のテストから影響を取り除き、システムをウォームアップするために必要です。



エラー処理を忘れないでください:



  1. プログラムは、すべてのファイルが読み取られた場合にのみ、戻りコード0を返します。



  2. システムが突然ファイルの読み取りを開始すると、テスト全体がクラッシュする場合があります。 ため息をついて再起動し、無視したファイルにファイル(またはフォルダー)を追加します。 WindowsのProgram Filesディレクトリをファイルの優れたソースとして使用しているため、現実的にはディスク全体に広がっているため、一部のファイルはしばらくロックされる可能性があります。



  3. たとえば、プロセスが既に終了しているため、1つのパフォーマンスカウンターがエラーをスローすることがあります。 この場合、この秒のすべてのカウンターは無視されます。



  4. 大きなファイルでは、一部のテストでメモリ不足例外が安定してスローされました。 結果からそれらを削除しました。



さらに、負荷テストに関する標準的な瞬間:



  1. コンパイル-MSVSのリリースモード。 起動は、デバッガーなどを使用しない別個のアプリケーションとして実行されます。チェックの本質は、通常のソフトウェアでファイルをより速く読み取る方法であるため、チューニングはありません。



  2. ウイルス対策が無効になり、システムの更新が停止し、アクティブなプログラムも停止します。 同じ理由で、これ以上チューニングはありませんでした。



  3. 各テストは、個別のプロセスの開始です。 オーバーヘッドはエラーの範囲内(つまり、jit、プロセスの開始に費やしたなど)であることが判明したため、このような隔離だけを残しました。



  4. 一部のパフォーマンスカウンターでは、HDD / SSDの結果が常にゼロでした。 カウンターのセットがプログラムに縫い込まれているので、私はそれらを残しました。



  5. すべてのプログラムはx64として起動しました。スワップを試みるとメモリが非効率になり、実行時間が長いために統計がすぐに低下しました。



  6. スレッドの優先度と他のチューニングは使用されませんでした。最大値を厳密に絞ろうとする試みがなかったためです(これは非常に多くの要因に大きく依存します)。

  7. テクノロジー:.Net 4.6、x64



結果



ヘッダーに書いたように、結果はGithubにあります: SSDHDD



SSDドライブ



最小ファイルサイズ(バイト):2、最大サイズ(バイト):25720320、平均サイズ(バイト):40953.1175

スクリプト

時間

ScenarioAsyncWithMaxParallelCount4

00:00:00.2260000

ScenarioAsyncWithMaxParallelCount8

00:00:00.5080000

ScenarioAsyncWithMaxParallelCount16

00:00:00.1120000

ScenarioAsyncWithMaxParallelCount24

00:00:00.1540000

ScenarioAsyncWithMaxParallelCount32

00:00:00.2510000

ScenarioAsyncWithMaxParallelCount64

00:00:00.5240000

ScenarioAsyncWithMaxParallelCount128

00:00:00.5970000

ScenarioAsyncWithMaxParallelCount256

00:00:00.7610000

ScenarioSyncAsParallel

00:00:00.9340000

ScenarioReadAllAsParallel

00:00:00.3360000

シナリオ非同期

00:00:00.8150000

シナリオAsync2

00:00:00.0710000

ScenarioNewThread

00:00:00.6320000



したがって、多くの小さなファイルを読み取るとき、2つの勝者は非同期操作です。 実際、どちらの場合でも.Netは31スレッドを使用しました。



実際、両方のプログラムは、ScenarioAsyncWithMaxParallelCount32のActionBlockの有無(制限付き)で異なっていたため、読み取りを制限しない方がよいため、より多くのメモリが使用され(私の場合は1.5回)、制限は単に標準設定レベルになります(スレッドプールはコアの数などに依存するため)



最小ファイルサイズ(バイト):1001、最大サイズ(バイト):25720320、平均サイズ(バイト):42907.8608

スクリプト

時間

ScenarioAsyncWithMaxParallelCount4

00:00:00.4070000

ScenarioAsyncWithMaxParallelCount8

00:00:00.2210000

ScenarioAsyncWithMaxParallelCount16

00:00:00.1240000

ScenarioAsyncWithMaxParallelCount24

00:00:00.2430000

ScenarioAsyncWithMaxParallelCount32

00:00:00.3180000

ScenarioAsyncWithMaxParallelCount64

00:00:00.5100000

ScenarioAsyncWithMaxParallelCount128

00:00:00.7270000

ScenarioAsyncWithMaxParallelCount256

00:00:00.8190000

ScenarioSyncAsParallel

00:00:00.7590000

ScenarioReadAllAsParallel

00:00:00.3120000

シナリオ非同期

00:00:00.5080000

シナリオAsync2

00:00:00.0670000

ScenarioNewThread

00:00:00.6090000



最小ファイルサイズを大きくすると、次のようになります。



  1. リーダーは、プロセッサコアの数に近いスレッド数でプログラムを開始しました。

  2. 一連のテストでは、スレッドの1つがロックが解放されるのを常に待っていました( パフォーマンスカウンター「同時キューの長さ」を参照)。

  3. ディスクからの同期読み取り方法は、依然として部外者です。



最小ファイルサイズ(バイト):10007、最大サイズ(バイト):62 444 171、平均サイズ(バイト):205102.2773

スクリプト

時間

ScenarioAsyncWithMaxParallelCount4

00:00:00.6830000

ScenarioAsyncWithMaxParallelCount8

00:00:00.5440000

ScenarioAsyncWithMaxParallelCount16

00:00:00.6620000

ScenarioAsyncWithMaxParallelCount24

00:00:00.8690000

ScenarioAsyncWithMaxParallelCount32

00:00:00.5630000

ScenarioAsyncWithMaxParallelCount64

00:00:00.2050000

ScenarioAsyncWithMaxParallelCount128

00:00:00.1600000

ScenarioAsyncWithMaxParallelCount256

00:00:00.4890000

ScenarioSyncAsParallel

00:00:00.7090000

ScenarioReadAllAsParallel

00:00:00.9320000

シナリオ非同期

00:00:00.7160000

シナリオAsync2

00:00:00.6530000

ScenarioNewThread

00:00:00.4290000



そして、SSDの最後のテスト:10 KBからのファイル、それらの数は小さいが、それら自体は大きい。 結果として:



  1. スレッドの数を制限しない場合、読み取り時間は同期操作に近くなります

  2. 制限は(コアの数)* [2.5-5.5]としてより望ましいです



HDDドライブ



SSDですべてが多かれ少なかれ良ければ、私はより頻繁にドロップしたので、クラッシュしたプログラムの結果の一部を除外しました。



最小ファイルサイズ(バイト):1001、最大サイズ(バイト):54989002、平均サイズ(バイト):210818,0652

スクリプト

時間

ScenarioAsyncWithMaxParallelCount4

00:00:00.3410000

ScenarioAsyncWithMaxParallelCount8

00:00:00.3050000

ScenarioAsyncWithMaxParallelCount16

00:00:00.2470000

ScenarioAsyncWithMaxParallelCount24

00:00:00.1290000

ScenarioAsyncWithMaxParallelCount32

00:00:00.1810000

ScenarioAsyncWithMaxParallelCount64

00:00:00.1940000

ScenarioAsyncWithMaxParallelCount128

00:00:00.4010000

ScenarioAsyncWithMaxParallelCount256

00:00:00.5170000

ScenarioSyncAsParallel

00:00:00.3120000

ScenarioReadAllAsParallel

00:00:00.5190000

シナリオ非同期

00:00:00.4370000

シナリオAsync2

00:00:00.5990000

ScenarioNewThread

00:00:00.5300000



小さなファイルの場合、リーダーは再び非同期読み取りです。 ただし、同期操作も良い結果を示しました。 答えはディスクの負荷、つまり同時読み取りの制限にあります。 多くのスレッドで強制的に読み取りを開始しようとすると、システムは読み取りのために大きなキューに置かれます。 その結果、並列作業の代わりに、多くの要求を並列に処理しようとすることに時間がかかります。



最小ファイルサイズ(バイト):1001、最大サイズ(バイト):54989002、平均サイズ(バイト):208913,2665

スクリプト

時間

ScenarioAsyncWithMaxParallelCount4

00:00:00.6880000

ScenarioAsyncWithMaxParallelCount8

00:00:00.2160000

ScenarioAsyncWithMaxParallelCount16

00:00:00.5870000

ScenarioAsyncWithMaxParallelCount32

00:00:00.5700000

ScenarioAsyncWithMaxParallelCount64

00:00:00.5070000

ScenarioAsyncWithMaxParallelCount128

00:00:00.4060000

ScenarioAsyncWithMaxParallelCount256

00:00:00.4800000

ScenarioSyncAsParallel

00:00:00.4680000

ScenarioReadAllAsParallel

00:00:00.4680000

シナリオ非同期

00:00:00.3780000

シナリオAsync2

00:00:00.5390000

ScenarioNewThread

00:00:00.6730000



中サイズのファイルの場合、スレッド数をさらに低い値に制限することが望ましいことを除いて、非同期読み取りの結果は引き続き良好です。



最小ファイルサイズ(バイト):10008、最大サイズ(バイト):138634176、平均サイズ(バイト):429888,6019

スクリプト

時間

ScenarioAsyncWithMaxParallelCount4

00:00:00.5230000

ScenarioAsyncWithMaxParallelCount8

00:00:00.4110000

ScenarioAsyncWithMaxParallelCount16

00:00:00.4790000

ScenarioAsyncWithMaxParallelCount24

00:00:00.3870000

ScenarioAsyncWithMaxParallelCount32

00:00:00.4530000

ScenarioAsyncWithMaxParallelCount64

00:00:00.5060000

ScenarioAsyncWithMaxParallelCount128

00:00:00.5810000

ScenarioAsyncWithMaxParallelCount256

00:00:00.5540000

ScenarioReadAllAsParallel

00:00:00.5850000

シナリオ非同期

00:00:00.5530000

シナリオAsync2

00:00:00.4440000



繰り返しますが、リーダーは非同期操作であり、並列操作の数に制限があります。 さらに、推奨されるスレッド数はさらに少なくなりました。 そして、並列同期読み取りは着実にメモリ不足を示し始めました。



ファイルサイズが大きくなると、同時読み取り数に制限のないスクリプトはメモリ不足に陥る可能性が高くなります。 結果は発売から発売まで安定していなかったので、私はすでにそのようなテストは不適切であると考えました。



まとめ



これらのテストからどのような結果を収集できますか?






All Articles