web-dev-qa-db-ja.com

Parallel.ForEachはForEachよりも遅い

コードは次のとおりです。

using (var context = new AventureWorksDataContext())
{
    IEnumerable<Customer> _customerQuery = from c in context.Customers
                                           where c.FirstName.StartsWith("A")
                                           select c;

    var watch = new Stopwatch();
    watch.Start();

    var result = Parallel.ForEach(_customerQuery, c => Console.WriteLine(c.FirstName));

    watch.Stop();
    Debug.WriteLine(watch.ElapsedMilliseconds);

    watch = new Stopwatch();
    watch.Start();

    foreach (var customer in _customerQuery)
    {
        Console.WriteLine(customer.FirstName);
    }

    watch.Stop();
    Debug.WriteLine(watch.ElapsedMilliseconds);
}

問題は、 Parallel.ForEachは通常のforeachと比較して約400ミリ秒かかり、約40ミリ秒かかります。私は正確に何を間違っているのですか、そしてなぜこれは私が期待するように機能しないのですか?

29
Mike Diaz

実行するタスクがあるとします。あなたが数学の教師であり、20点の採点があるとします。論文の採点には2分かかるので、約40分かかります。

ここで、論文の採点を支援するアシスタントを雇うことにしたとしましょう。 4人のアシスタントを見つけるのに1時間かかります。あなたはそれぞれ4つの論文を取り、あなたはすべて8分で完了します。アシスタントを見つけるための追加の時間を含めて、合計68分の作業で40分の作業を交換したので、これは節約になりません。アシスタントを見つけるためのオーバーヘッドは、自分で作業を行うコストよりも大きくなります。

ここで、採点する論文が2万枚あるとすると、約40000分かかります。今、あなたがアシスタントを見つけるのに1時間費やすなら、それは勝利です。あなたはそれぞれ4000枚の論文を取り、40000分ではなく合計8060分で完了します。これは、約5分の1の節約になります。アシスタントを見つけるオーバーヘッドは基本的に関係ありません。

並列化は自由ではありません異なるスレッド間で作業を分割するコストは、スレッドごとに実行される作業量に比べて小さい必要があります。

参考文献:

https://en.wikipedia.org/wiki/Amdahl%27s_law

https://en.wikipedia.org/wiki/Gustafson%27s_law

153
Eric Lippert

最初に理解しておくべきことは、すべての並列処理が有益であるとは限らないということです。並列処理にはある程度のオーバーヘッドがあり、このオーバーヘッドは、並列化される内容の複雑さに応じて重要な場合と重要でない場合があります。並列関数での作業は非常に小さいため、並列処理が行う必要のある管理のオーバーヘッドが大きくなり、全体的な作業が遅くなります。

9
skaz

数えられるVSのすべてのスレッドを作成し、数えられるものを実行するだけの追加のオーバーヘッドは、おそらく速度低下の原因です。 Parallel.ForEachは、包括的なパフォーマンス向上の動きではありません。各要素に対して完了する予定の操作がブロックされる可能性があるかどうかを比較検討する必要があります。

たとえば、単にコンソールに書き込むのではなく、Webリクエストなどを行う場合、並列バージョンの方が高速になる可能性があります。現状では、コンソールへの単純な書き込みは非常に高速な操作であるため、スレッドを作成してスレッドを開始するオーバーヘッドは遅くなります。

9
Tejs

以前のライターがParallel.ForEachに関連するオーバーヘッドがあると述べましたが、それはあなたがパフォーマンスの向上を見ることができない理由ではありません。 Console.WriteLineは同期操作であるため、一度に1つのスレッドのみが動作しています。ボディをブロックしないものに変更してみてください。パフォーマンスが向上します(ボディの作業量がオーバーヘッドを上回るのに十分な大きさである限り)。

4
salomons

私はサロモンの答えが好きで、あなたにも追加のオーバーヘッドがあることを付け加えたいと思います

  1. 代理人の割り当て。
  2. それらを介して呼び出します。
0
Yola