web-dev-qa-db-ja.com

Parallel.ForEachとTask.Factory.StartNew

以下のコードスニペットの違いは何ですか?両方ともスレッドプールスレッドを使用していませんか?

たとえば、コレクション内の各アイテムに対して関数を呼び出したい場合、

Parallel.ForEach<Item>(items, item => DoSomething(item));

vs

foreach(var item in items)
{
  Task.Factory.StartNew(() => DoSomething(item));
}
255

最初の方法は、はるかに優れたオプションです。

Parallel.ForEachは、内部で Partitioner<T> を使用して、コレクションを作業項目に配布します。アイテムごとに1つのタスクを行うのではなく、これをバッチ処理してオーバーヘッドを削減します。

2番目のオプションは、コレクション内のアイテムごとに単一のTaskをスケジュールします。結果は(ほぼ)同じですが、これにより、特に大規模なコレクションの場合、必要以上のオーバーヘッドが発生し、全体的なランタイムが遅くなります。

参考-必要に応じて、適切な Parallel.ForEachへのオーバーロード を使用して、使用するパーティショナーを制御できます。詳細については、MSDNの Custom Partitioners を参照してください。

実行時の主な違いは、2番目が非同期に動作することです。これは、Parallel.ForEachを使用して次のように複製できます。

Task.Factory.StartNew( () => Parallel.ForEach<Item>(items, item => DoSomething(item)));

これにより、パーティショナーを利用できますが、操作が完了するまでブロックしないでください。

287
Reed Copsey

「Parallel.For」と「Task」オブジェクトでメソッドを「1000000000」回実行する小さな実験を行いました。

プロセッサ時間を測定したところ、Parallelの方が効率的でした。 Parallel.Forは、タスクを小さな作業項目に分割し、最適な方法ですべてのコアで並列に実行します。多くのタスクオブジェクトを作成する間(FYI TPLは内部でスレッドプーリングを使用します)、各タスクのすべての実行を移動して、以下の実験から明らかなように、ボックス内により多くのストレスを作成します。

また、基本的なTPLを説明し、Parallel.Forがコアをより効率的に利用する方法を示す小さなビデオを作成しました http://www.youtube.com/watch?v=No7QqSc5cl8 通常のタスクと比較しておよびスレッド。

実験1

Parallel.For(0, 1000000000, x => Method1());

実験2

for (int i = 0; i < 1000000000; i++)
{
    Task o = new Task(Method1);
    o.Start();
}

Processor time comparison

80

Parallel.ForEachは、ループが終了するまで最適化し(新しいスレッドを開始しないこともあります)、ブロックします。Task.Factoryは、各項目に対して明示的に新しいタスクインスタンスを作成し、終了する前に戻ります(非同期タスク)。 Parallel.Foreachははるかに効率的です。

17
Sogger

私の見解では、最も現実的なシナリオは、タスクを完了するために重い操作がある場合です。 Shivprasadのアプローチは、コンピューティング自体よりもオブジェクトの作成/メモリの割り当てに重点を置いています。私は次のメソッドを呼び出す研究を行いました:

public static double SumRootN(int root)
{
    double result = 0;
    for (int i = 1; i < 10000000; i++)
        {
            result += Math.Exp(Math.Log(i) / root);
        }
        return result; 
}

このメソッドの実行には約0.5秒かかります。

Parallelを使用して200回呼び出しました。

Parallel.For(0, 200, (int i) =>
{
    SumRootN(10);
});

次に、昔ながらの方法で200回呼び出しました。

List<Task> tasks = new List<Task>() ;
for (int i = 0; i < loopCounter; i++)
{
    Task t = new Task(() => SumRootN(10));
    t.Start();
    tasks.Add(t);
}

Task.WaitAll(tasks.ToArray()); 

最初のケースは26656msで完了し、2番目のケースは24478msで完了しました。何回も繰り返しました。 2番目のアプローチの方がわずかに高速です。

8
user1089583