11

重複の可能性:
Parallel.ForEachとTask.Factory.StartNew

毎晩約1,000のタスクを実行する必要がありThreadPoolます(将来的にはその数が増える可能性があります)。各タスクは長時間実行される操作(Webサービスからのデータの読み取り)を実行しており、CPUを集中的に使用しませんAsync I/Oこの特定のユースケースのオプションではありません。

IList<string>のパラメータが与えられた場合、私はする必要がありますDoSomething(string x)。私は次の2つのオプションから選択しようとしています:

IList<Task> tasks = new List<Task>();
foreach (var p in parameters)
{
    tasks.Add(Task.Factory.StartNew(() => DoSomething(p), TaskCreationOptions.LongRunning));
}
Task.WaitAll(tasks.ToArray());

また

Parallel.ForEach(parameters, new ParallelOptions {MaxDegreeOfParallelism = Environment.ProcessorCount*32}, DoSomething);

どちらのオプションが優れているのか、そしてその理由は何ですか?

ノート :

答えには、との使用法の比較を含める必要がTaskCreationOptions.LongRunningありMaxDegreeOfParallelism = Environment.ProcessorCount * SomeConstantます。

4

3 に答える 3

36

おそらくあなたはこれに気づいていませんが、Parallelクラスのメンバーは単にTaskオブジェクトの(複雑な)ラッパーです。ご参考までに、Parallelクラスは。を使用してTaskオブジェクトを作成しますTaskCreationOptions.None。ただし、MaxDegreeOfParallelismタスクオブジェクトのコンストラクターに渡された作成オプションに関係なく、これらのタスクオブジェクトに影響します。

TaskCreationOptions.LongRunningTaskSchedulerスレッドのオーバーサブスクリプションを使用するとパフォーマンスが向上する可能性があるという、基礎となる「ヒント」を提供します。オーバーサブスクリプションは、I / Oなどの待ち時間の長いスレッドに適しています。これは、単一のコアに複数のスレッド(はい、タスクではなくスレッド)を割り当てるため、待機するのではなく、常に何かを行う必要があるためです。スレッドが待機状態にある間に完了する操作。TaskSchedulerを使用するThreadPool場合、LongRunningタスクは専用のスレッドで実行されます(タスクごとにスレッドがある場合のみ)。それ以外の場合は、スケジューリングと作業の盗用(とにかくここで必要なもの)を使用して正常に実行されます

MaxDegreeOfParallelism実行される同時操作の数を制御します。これは、データが分割されて処理されるパーティションの最大数を指定するのと似ています。指定できた場合、これは、この例のように、最大​​同時実行レベルがその値に設定されているTaskCreationOptions.LongRunningタスクと同様に、一度に実行されるタスクの数を制限することです。TaskScheduler

が必要な場合がありParallel.ForEachます。MaxDegreeOfParallelismただし、このような高い数に等しい値を追加しても、タスクは引き続きによって制御されるため、実際には一度に多くのスレッドが実行されることは保証されませんThreadPoolTaskScheduler。そのスケジューラーは、一度に実行されるスレッドの数を可能な限り最小限に抑えます。これが、2つの方法の最大の違いだと思います。TaskScheduler最大限の並列処理動作を模倣し、両方の長所を備えた独自の記述(および指定)を行うこともできますが、あなたが興味を持っていることには疑問があります。

私の推測では、レイテンシーと実行する必要のある実際のリクエストの数に応じて、タスクの使用は多くの(?)ケースでパフォーマンスが向上しますが、最終的にはより多くのメモリを使用することになりますが、並列ではリソース使用量の一貫性が高まります。もちろん、非同期I / Oは、これら2つのオプションのいずれよりも非常に優れたパフォーマンスを発揮しますが、レガシーライブラリを使用しているため、それができないことを理解しています。そのため、残念ながら、どちらを選択しても、平凡なパフォーマンスにとどまります。

本当の解決策は、非同期I/Oを実現する方法を見つけることです。状況がわからないので、それ以上に役立つとは思いません。プログラム(読み取り、スレッド)は実行を継続し、カーネルはI / O操作が完了するのを待ちます(これはI / O完了ポートの使用とも呼ばれます)。スレッドは待機状態ではないため、ランタイムはより少ないスレッドでより多くの作業を実行できます。これは通常、コアの数とスレッドの数の間の最適な関係になります。スレッドを追加しても、パフォーマンスが向上するわけではありません(実際には、コンテキストスイッチングなどの理由で、パフォーマンスが低下することがよくあります)。

ただし、この回答全体は、質問の最終的な回答を決定するのに役立ちませんが、必要な方向性が得られることを願っています。プロファイルを作成するまで、何がより優れたパフォーマンスを発揮するかはわかりません。両方を試さず(LongRunningオプションのないタスクを意味し、スケジューラーにスレッドの切り替えを処理させることを明確にする必要があります)、特定のユースケースに最適なものを決定するためにそれらをプロファイリングすると、売り切れになります。

于 2012-05-21T18:02:40.990 に答える
4

これは直接の比較ではありませんが、役立つと思います。私はあなたが説明したのと同じようなことをします(私の場合、REST呼び出しを提供するもう一方の端に負荷分散されたサーバークラスターがあることを知っています)。次のコードを使用して、通常よりも多くのエンドポイントに接続できることをオペレーティングシステムに通知することParrallel.ForEachで、最適な数のワーカースレッドを起動することで良好な結果が得られます。

    var servicePointManager = System.Net.ServicePointManager.FindServicePoint(Uri);
    servicePointManager.ConnectionLimit = 250;

接続する一意のURLごとに1回呼び出す必要があることに注意してください。

于 2012-05-21T19:17:11.030 に答える
4

どちらのオプションも、シナリオにはまったく不適切です。

TaskCreationOptions.LongRunningParallelTPL(クラス/拡張機能)は、CPUにバインドされた操作を複数のコア(スレッドではなく)で実行することにより、CPUにバインドされた操作のスループットを最大化することのみを目的としているため、CPUにバインドされていないタスクには確かに適しています。

ただし、1000タスクはこれには受け入れられない数です。それらがすべて一度に実行されているかどうかは、正確には問題ではありません。同期I/Oを待機している100スレッドでさえ、受け入れがたい状況です。コメントの1つが示唆しているように、アプリケーションは膨大な量のメモリを使用し、ほとんどすべての時間をコンテキスト切り替えに費やすことになります。TPLはこの規模向けには設計されていません。

操作がI/Oバウンドであり、Webサービスを使用している場合は非同期I / Oが正しいソリューションであるだけでなく、それが唯一のソリューションです。コードの一部を再構築する必要がある場合(たとえば、元々存在しなかった主要なインターフェイスに非同期メソッドを追加するなど)、I/O完了ポートがWindowsまたは.NETの唯一のメカニズムであるために実行します。この特定のタイプの同時実行を適切にサポートできます。

非同期I/Oがどういうわけか「オプションではない」という状況を聞いたことがありません。この制約の有効なユースケースを思いつくことすらできません。非同期I/Oを使用できない場合、これは、できるだけ早く修正する必要のある重大な設計上の問題を示しています。

于 2012-05-21T18:13:32.593 に答える