2012-02-29 21 views
3

なぜId = 999999が述語を使用したラムダ式駆動型比較より高速ですか?オープン型述語がなぜそれよりも遅い

多分私のテスト自体は100%同じではないかもしれませんが、これは一般的な質問について何かを示すサンプルです:述語は一般的なId == 999999よりも遅いですか?

述部には150msがかかりますが、共通比較には125msがかかります。どこから来たの

違い/オーバーヘッド?あなたはなぜ私が25ミリ秒気にするのか聞いてみるかもしれませんまあ...私は階層型のfindメソッドでもオープン型の述語を使用していますが、そこには格差があります。

だから私はラムダ(各 "u"デリゲートを作成する)+述語が問題だと思いますか?セットアップに何が間違っていないのでしょうか?

public class UtilitiesTest 
     { 
      [Test] 
      public void Go() 
      { 
       var units = GetUnits(); 
       DateTime d = DateTime.Now;   
       var item = units.testme<MyUnit>(u => u.Id == 999999); 

       TimeSpan t = DateTime.Now - d; 

       Debug.WriteLine(t.TotalMilliseconds + " ms"); 


       var units1 = GetUnits(); 
       DateTime d1 = DateTime.Now; 

       MyUnit item1 = null; 
       foreach (MyUnit unit in units1) 
       { 
        if (unit.Id == 999999) 
        { 
         item1 = unit; 
         break; 
        } 
       } 
       TimeSpan t1 = DateTime.Now - d1; 
       Debug.WriteLine(t1.TotalMilliseconds + " ms"); 
      } 

      private IEnumerable<MyUnit> GetUnits() 
      { 
       for (int i = 0; i < 1000000; i++)   
        yield return new MyUnit() { Id = i };    
      }   
     } 

     class MyUnit 
     { 
      public int Id { get; set; } 
     } 

    public static T testme<T>(this IEnumerable<T> source, Predicate<T> condition) where T : class 
      { 
       foreach (T item in source) 
       { 
        if (condition(item)) 
        { 
         return item; 
        } 
       } 
       return default(T); 
      } 

答えて

3

私は速度の小さな差があると思いますが、私の最初のポイントは、私はあなたのテストでの改善したいいくつかのことがあるということでしょう。これらマイクロベンチマークテストを構築する場合、常にいくつかのルールを遵守することが重要である:

タイミング、代わりのDateTimeのストップウォッチを使用し
  1. - それは、より高い解像度を持ち、より正確です。
  2. は、実行する前にすべてのコードを一度テストしてください。さもなければ、コードの最初のビットは、JITの大部分のコストを負うので、遅くなる傾向があります。
  3. テストは常に複数回実行してください。私はあなたのテストを手直しした場合

が、私はこのようなもので終わる:私は、しかし

Test_Lambda: 68 ms 
Test_Equality: 53 ms 

全体:私はこれを実行する

public void Go() 
{ 
    // warmup 
    Test_Equality(); 
    Test_Lambda(); 

    // timed tests 
    Console.WriteLine(Test_Equality() + " ms"); 
    Console.WriteLine(Test_Lambda() + " ms"); 
} 

public long Test_Lambda() 
{ 
    var units1 = GetUnits(); 
    var stopWatch1 = new Stopwatch(); 
    stopWatch1.Start(); 
    MyUnit item1 = units1.testme<MyUnit>(u => u.Id == 999999); 
    return stopWatch1.ElapsedMilliseconds; 
} 

public long Test_Equality() 
{ 
    var units2 = GetUnits(); 
    var stopWatch2 = new Stopwatch(); 
    stopWatch2.Start(); 
    MyUnit item2; 
    foreach (MyUnit unit in units2) 
    { 
     if (unit.Id == 999999) 
     { 
      item2 = unit; 
      break; 
     } 
    } 

    return stopWatch2.ElapsedMilliseconds; 
} 

、私は大体このある数字を取得しますメソッドを直接呼び出すのではなく、デリゲートを介してメソッドを呼び出すことと比べてパフォーマンスが低下するのと同じ方法で、ネイティブ呼び出しに対してデリゲート/ラムダバージョンを呼び出すときにパフォーマンスが低下することが予想されます。その背後では、コンパイラはこれらのラムダバージョンのテストをサポートするための特別なコードを生成しています。

は、最終的にはそれがこの線に沿って何か を生成している:

public class PossibleLambdaImpl 
{ 
    public bool Comparison(MyUnit myUnit) 
    { 
     return myUnit.Id == 9999999; 
    } 
} 

したがって、あなたのラムダテストは、実際にコンパイラが生成したクラスのメソッド、それが評価されるたびに呼びかけています。実際に

- 私が代わりに一回以上PossibleLambdaImplクラスを作成するためにあなたの平等のテストを変更し、ループラウンドPossibleLambdaImpl.Comparisonたびに呼び出すとき、私はラムダケースとほぼ同じ結果を得る:

public long Test_PossibleLambdaImpl() 
{ 
    var units2 = GetUnits(); 
    var stopWatch2 = new Stopwatch(); 
    stopWatch2.Start(); 
    MyUnit item2; 
    var possibleLambdaImpl = new PossibleLambdaImpl(); 
    foreach (MyUnit unit in units2) 
    { 
     if (possibleLambdaImpl.Comparison(unit)) 
     { 
      item2 = unit; 
      break; 
     } 
    } 
    return stopWatch2.ElapsedMilliseconds; 
} 

[注:私よりもこのことについてはるかに多くを知っている、このサイト上で他の人がある - しかし、大まかに言えば、私はこれが正しいと信じている]

は、いずれの場合も、覚えておくべき事は、このパフォーマンスの違いはごくわずかですということです。このようなマイクロベンチマークは常にその差を強調します。あなたのテストによれば、それらの間に10%-20%の性能差があるかもしれませんが、実際のコードがこの種の呼び出しを行う時間の0.001%を費やすだけであれば、これは絶対的に小さな違いになります実行コード。

+0

あなたのメソッド名が変更されていると思います。 Test_LambdaメソッドはLambdasを持たず、Test_Equalityメソッドはラムダ – Stilgar

+0

@Stilgar - 既に修正されていますが、よく見えます。最後の変更は、コードをより読みやすくするために間違っていました。 –

+0

@Stilgarロブはそれを編集するために25秒を必要と:P – Pascal

0

私は、デリゲートが遅くなることを期待します。結局のところ、デリゲート引数をレジスタなどにロードしてから、もう1つのジャンプ命令であるメソッド呼び出しを実行する必要があります。なぜあなたはそれらが同じであると思いますか?

+0

私はその違いを見て、私は本当に期待していません。私は理由を知りたい。 – Pascal

+0

マシンはメソッドを呼び出す必要があります。これは、いくつかのサイクルがそこで費やされることを意味します。代理人は、通常の方法のようにインライン化することはできません。 – Stilgar

関連する問題