
こんにちは、Habr! しばらく前、私は質問に興味がありました:ディスクからデータを読み取る最も効率的な方法は何ですか(.Netがある場合)? 多数のファイルを読み取るタスクは、多くのプログラムで見られます。多くのプログラムは、最初から構成の読み取りを開始し、一部のロードモジュールを独自に読み込みます。
インターネットでは、このような比較は見つかりませんでした(特定の構成のチューニングを除く)。
結果はGithubで見ることができます: SSD 、 HDD 。
読み取り方法とテストアルゴリズム
基本的な方法がいくつかあります。
- ScenarioReadAllAsParallel- ThreadPoolでReadAllTextを使用して読み取り 。
- ScenarioSyncAsParallel- スレッドプールでStream'ovを同期的に使用して読み取ります。
- ScenarioNewThread- Stream'ovを同期的に使用して読み取り、読み取りごとに個別のスレッドで、新しいスレッドを開始する時間も考慮されます 。
- ScenarioAsync2- Streamを非同期的に使用して読み取ります(つまり、非同期/待機、ファイルシステムの応答に時間がかかる場合、多くの操作を並行して開始できます) ;
- ScenarioAsync- 非同期読み取り。ただし、開始は多くのスレッドで発生します(前のテストのように、同じメインスレッドで連続して発生しません) 。
- ScenarioAsyncWithMaxParallelCount- Stream'ovを非同期的に使用して読み取ります(つまりasync / await)が、N個の並列操作よりも多くはありません 。
SSDとHDDですべてをテストしました(最初のケースではXeon 24コアと16 GBのメモリとIntel SSDを搭載したコンピューターがあり、2つ目はCore i5、4 GBのRAMと5400 rpmのHDDを搭載したMac Mini MGEM2LL / Aでした)。 システムは、結果に応じて、比較的新しいシステムではなく、比較的新しいシステムで最も適切に動作する方法を理解できるようになっています。
プロジェクトはここで表示できます 。これは、1つのメインTestsHost実行可能ファイルと、シナリオ*という名前の一連のプロジェクトです。 各テストは次のとおりです。
- 正味時間を計算するexeファイルを実行します。
- 1秒に1回、プロセッサの負荷、RAMの消費、ディスクの負荷、およびその他の多数の派生パラメーターがチェックされます( パフォーマンスカウンターを使用)。
- 結果は記憶され、テストは数回繰り返されます。 最終結果は、最大値と最小値を除いた平均時間です。
テストの準備は複雑です。 したがって、開始する前に:
- ファイルのサイズと数を決定します(ディスクキャッシュの影響を抑えるために、合計ボリュームがRAMサイズよりも大きくなるように選択しました)。
- コンピューター上で特定のサイズのファイルを探しています(同時に、以下で説明するアクセスできないファイルといくつかの特別なフォルダーを無視します)。
- ファイルのセットに対してテストの1つを実行しますが、結果は無視します。 これはすべて、OSキャッシュをリセットし、以前のテストから影響を取り除き、システムをウォームアップするために必要です。
エラー処理を忘れないでください:
- プログラムは、すべてのファイルが読み取られた場合にのみ、戻りコード0を返します。
- システムが突然ファイルの読み取りを開始すると、テスト全体がクラッシュする場合があります。 ため息をついて再起動し、無視したファイルにファイル(またはフォルダー)を追加します。 WindowsのProgram Filesディレクトリをファイルの優れたソースとして使用しているため、現実的にはディスク全体に広がっているため、一部のファイルはしばらくロックされる可能性があります。
- たとえば、プロセスが既に終了しているため、1つのパフォーマンスカウンターがエラーをスローすることがあります。 この場合、この秒のすべてのカウンターは無視されます。
- 大きなファイルでは、一部のテストでメモリ不足例外が安定してスローされました。 結果からそれらを削除しました。
さらに、負荷テストに関する標準的な瞬間:
- コンパイル-MSVSのリリースモード。 起動は、デバッガーなどを使用しない別個のアプリケーションとして実行されます。チェックの本質は、通常のソフトウェアでファイルをより速く読み取る方法であるため、チューニングはありません。
- ウイルス対策が無効になり、システムの更新が停止し、アクティブなプログラムも停止します。 同じ理由で、これ以上チューニングはありませんでした。
- 各テストは、個別のプロセスの開始です。 オーバーヘッドはエラーの範囲内(つまり、jit、プロセスの開始に費やしたなど)であることが判明したため、このような隔離だけを残しました。
- 一部のパフォーマンスカウンターでは、HDD / SSDの結果が常にゼロでした。 カウンターのセットがプログラムに縫い込まれているので、私はそれらを残しました。
- すべてのプログラムはx64として起動しました。スワップを試みるとメモリが非効率になり、実行時間が長いために統計がすぐに低下しました。
- スレッドの優先度と他のチューニングは使用されませんでした。最大値を厳密に絞ろうとする試みがなかったためです(これは非常に多くの要因に大きく依存します)。
- テクノロジー:.Net 4.6、x64
結果
ヘッダーに書いたように、結果はGithubにあります: SSD 、 HDD 。
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つがロックが解放されるのを常に待っていました( パフォーマンスカウンター「同時キューの長さ」を参照)。
- ディスクからの同期読み取り方法は、依然として部外者です。
最小ファイルサイズ(バイト):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からのファイル、それらの数は小さいが、それら自体は大きい。 結果として:
- スレッドの数を制限しない場合、読み取り時間は同期操作に近くなります
- 制限は(コアの数)* [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
|
繰り返しますが、リーダーは非同期操作であり、並列操作の数に制限があります。 さらに、推奨されるスレッド数はさらに少なくなりました。 そして、並列同期読み取りは着実にメモリ不足を示し始めました。
ファイルサイズが大きくなると、同時読み取り数に制限のないスクリプトはメモリ不足に陥る可能性が高くなります。 結果は発売から発売まで安定していなかったので、私はすでにそのようなテストは不適切であると考えました。
まとめ
これらのテストからどのような結果を収集できますか?
- ほとんどすべての場合、同期読み取りと比較して非同期読み取りの方が速度が最高になりました。
- ファイルサイズを大きくする場合、スレッド数を制限することをお勧めします。そうしないと、読み取りが遅くなり、さらにOOMのリスクが増加します。
- すべての場合において、生産性の根本的な大幅な増加はなく、最大2〜3倍でした。 したがって、非同期読み取り用に古いレガシーアプリケーションを書き換えないでください。
- ただし、新しい非同期プログラムの場合、ファイルアクセスは少なくともクラッシュの可能性を減らし、速度を向上させます。