2013-01-02 13 views
28

私のアプリケーションでは、数十から数百のアクションを並行して実行します(アクションの戻り値はありません)。 (actionsAction配列でParallelクラスを使用Actionアレイ(Action[]Task.Factory.StartNewとParallel.Invoke

Task.Factory.StartNew(() => someAction());

    1. foreachループの反復でTask.Factory.StartNewの使用:アプローチが最適であろう

      Action[]

      Parallel.Invoke(actions);

    同等これら二つのアプローチはありますか?パフォーマンスに何か影響はありますか?

    EDITは

    私はいくつかのパフォーマンステストを実施してきたし、私のマシン上で(2 CPU 2コアごと)の結果は非常に似ているように思えます。私はそれが1つのCPUのような他のマシンのように見えるかどうかはわかりません。また、私は(非常に正確な方法をテストする方法を知らない)確信していない何がメモリ消費量です。

  • +3

    私は彼らが多かれ少なかれ同等だと思う。あなたのプロファイラはあなたに何を伝えていますか? –

    +0

    あなたはベンチマークを検討しましたか? –

    +1

    2つの方法についてのこの素敵な小さな記事をチェックしてください:http://blackrabbitcoder.net/archive/2012/12/20/c.net-little-wonders-the-parallel.invoke-method.aspx。私はパフォーマンスの違いはないと思うが、私はParallel.Invokeが簡単だと思う。 –

    答えて

    41

    これら二つの最も重要な違いはStartNewタスクが自分で完了することができ、コードの次の行に移ります一方Parallel.Invokeは、コードを続行する前に完了するためにすべてのアクションを待つことになるということです良い時間。

    この意味上の違いは、最初の(そしてたぶん唯一の)考慮事項です。

    /* This is a benchmarking template I use in LINQPad when I want to do a 
    * quick performance test. Just give it a couple of actions to test and 
    * it will give you a pretty good idea of how long they take compared 
    * to one another. It's not perfect: You can expect a 3% error margin 
    * under ideal circumstances. But if you're not going to improve 
    * performance by more than 3%, you probably don't care anyway.*/ 
    void Main() 
    { 
        // Enter setup code here 
        var actions2 = 
        (from i in Enumerable.Range(1, 10000) 
        select (Action)(() => {})).ToArray(); 
    
        var awaitList = new Task[actions2.Length]; 
        var actions = new[] 
        { 
         new TimedAction("Task.Factory.StartNew",() => 
         { 
          // Enter code to test here 
          int j = 0; 
          foreach(var action in actions2) 
          { 
           awaitList[j++] = Task.Factory.StartNew(action); 
          } 
          Task.WaitAll(awaitList); 
         }), 
         new TimedAction("Parallel.Invoke",() => 
         { 
          // Enter code to test here 
          Parallel.Invoke(actions2); 
         }), 
        }; 
        const int TimesToRun = 100; // Tweak this as necessary 
        TimeActions(TimesToRun, actions); 
    } 
    
    
    #region timer helper methods 
    // Define other methods and classes here 
    public void TimeActions(int iterations, params TimedAction[] actions) 
    { 
        Stopwatch s = new Stopwatch(); 
        int length = actions.Length; 
        var results = new ActionResult[actions.Length]; 
        // Perform the actions in their initial order. 
        for(int i = 0; i < length; i++) 
        { 
         var action = actions[i]; 
         var result = results[i] = new ActionResult{Message = action.Message}; 
         // Do a dry run to get things ramped up/cached 
         result.DryRun1 = s.Time(action.Action, 10); 
         result.FullRun1 = s.Time(action.Action, iterations); 
        } 
        // Perform the actions in reverse order. 
        for(int i = length - 1; i >= 0; i--) 
        { 
         var action = actions[i]; 
         var result = results[i]; 
         // Do a dry run to get things ramped up/cached 
         result.DryRun2 = s.Time(action.Action, 10); 
         result.FullRun2 = s.Time(action.Action, iterations); 
        } 
        results.Dump(); 
    } 
    
    public class ActionResult 
    { 
        public string Message {get;set;} 
        public double DryRun1 {get;set;} 
        public double DryRun2 {get;set;} 
        public double FullRun1 {get;set;} 
        public double FullRun2 {get;set;} 
    } 
    
    public class TimedAction 
    { 
        public TimedAction(string message, Action action) 
        { 
         Message = message; 
         Action = action; 
        } 
        public string Message {get;private set;} 
        public Action Action {get;private set;} 
    } 
    
    public static class StopwatchExtensions 
    { 
        public static double Time(this Stopwatch sw, Action action, int iterations) 
        { 
         sw.Restart(); 
         for (int i = 0; i < iterations; i++) 
         { 
          action(); 
         } 
         sw.Stop(); 
    
         return sw.Elapsed.TotalMilliseconds; 
        } 
    } 
    #endregion 
    

    結果::

    Message    | DryRun1 | DryRun2 | FullRun1 | FullRun2 
    ---------------------------------------------------------------- 
    Task.Factory.StartNew | 43.0592 | 50.847 | 452.2637 | 463.2310 
    Parallel.Invoke  | 10.5717 | 9.948 | 102.7767 | 101.1158 
    

    あなたが見ることができるように、およそ4.5倍に高速にNEWアップタスクの束を待っているよりもすることができParallel.Invokeを使用してでも、情報提供の目的のために、ここでのベンチマークです完了する。もちろん、それはあなたの行動が絶対に何もしないときです。それぞれのアクションが多いほど、差異が少なくなります。

    +1

    タイミングの追加に感謝 – BigChief

    +0

    これも参照してください:http://stackoverflow.com/questions/16101811/task-waitall-method-vs-parallel-invoke-method – nawfal

    12

    大規模なスキームでは、どのような場合でも多くのタスクを実際に処理するオーバーヘッドを考慮すると、2つの方法のパフォーマンスの差はごくわずかです。

    Parallel.Invokeは、基本的にTask.Factory.StartNew()を実行します。ですから、私はここで可読性がより重要だと言います。 StriplingWarriorが言及として

    また、Parallel.Invokeあなたはどちらかそれをする必要はありませんので、あなたのための(すべてのタスクが完了するまでコードを遮断する)WaitAllを実行します。タスクが完了したときに注意を払わずにバックグラウンドで実行したい場合は、Task.Factory.StartNew()が必要です。

    12

    私はStriplingWarrorからのテストを使用して、その違いがどこから来ているかを調べました。私はこれを行いました。なぜなら、コードでReflectorを見ると、Parallelクラスはたくさんのタスクを作成して実行させることと何も変わりません。

    理論的な観点からは、両方の方法が実行時間の点で同等である必要があります。しかし、空のアクションを持つ(あまり現実的ではない)テストでは、Parallelクラスがはるかに高速であることがわかりました。

    タスクバージョンは、多くのガベージコレクションにつながる新しいタスクの作成にほとんど時間を費やしています。表示される速度の違いは、純粋にあなたがすぐにゴミになる多くのタスクを作成するという事実によるものです。

    代わりに、Parallelクラスは、すべてのCPUで同時に実行される独自のタスク派生クラスを作成します。すべてのコアで実行されるphyiscalタスクは1つだけです。同期は、現在、タスクデリゲートの内部で行われていますが、これはParallelクラスのはるかに高速です。

    ParallelForReplicatingTask task2 = new ParallelForReplicatingTask(parallelOptions, delegate { 
         for (int k = Interlocked.Increment(ref actionIndex); k <= actionsCopy.Length; k = Interlocked.Increment(ref actionIndex)) 
         { 
          actionsCopy[k - 1](); 
         } 
        }, TaskCreationOptions.None, InternalTaskOptions.SelfReplicating); 
    task2.RunSynchronously(parallelOptions.EffectiveTaskScheduler); 
    task2.Wait(); 
    

    だから何が良いですか?最良のタスクは決して実行されないタスクです。ガベージコレクタにとって負担になるような多くのタスクを作成する必要がある場合は、タスクAPIから離れ、新しいタスクなしですべてのコアで直接並列実行できるParallelクラスを使用してください。

    あなたも速く、それは手でスレッドを作成して、アクセスパターンのためにあなたの最高速度を与えるために手最適化されたデータ構造を使用すると、最もパフォーマンスの高いソリューションであることかもしれませんになるために必要がある場合。しかし、TPL APIとParallel APIが既に大きく調整されているため、成功することはまずありません。通常は、多くのオーバーロードのうちの1つを使用して実行中のタスクを構成するか、Parallelクラスを使用してコードを大幅に少なくする必要があります。

    しかし、標準以外のスレッドパターンを使用している場合は、コアを最大限に活用するためにTPLを使用しないほうがよい場合があります。スティーブン・トゥブ(Stephen Toub)も、TPL APIは超高速パフォーマンスのために設計されていないが、主な目的は「平均的な」プログラマのためのスレッド化を容易にすることであると述べました。特定のケースでTPLを克服するには、平均以上のものが必要であり、CPUキャッシュライン、スレッドスケジューリング、メモリモデル、JITコード生成などについて多くのことを知る必要があります。より良い。

    関連する問題