2012-07-26 5 views
5

私は単純なシナリオを持っています。ここでは、ストックオブジェクトのリストの式コンパイル済みツリーのパフォーマンスをテストしようとしています。以下はコードですコンパイルされた式ツリーのパフォーマンス

表現コンパイルツリーのパフォーマンスは、静的ラムダコールよりも5倍遅いです。これが表現されたコンパイルされたツリーで期待できる標準的なパフォーマンスかどうかはわかりません。どんな洞察にも感謝します。

LambdaExpression(); 
List<Stock> stocks = new List<Stock>(); 
for (int ctr = 0; ctr <= 5000000; ctr++) 
{ 
    Stock stk1 = new Stock() { Price = ctr, Symbol = "A", CloseDate = DateTime.Now, FaceValue = ctr } ; 
    stocks.Add(stk1); 
} 
CompileTimeLamda(a); 
DynamicLambda(a); 


public static void LambdaExpression() 
{ 
    ParameterExpression CS1 = Expression.Parameter(typeof(Stock), "d"); 

    var line1 = Expression.Equal(Expression.Property(CS1, typeof(Stock).GetProperty("Symbol")), Expression.Constant("MSFT", typeof(string))); 
    var line2 = Expression.GreaterThan(Expression.Property(Expression.Property(CS1, typeof(Stock).GetProperty("CloseDate")),typeof(DateTime).GetProperty("Millisecond")), 
           Expression.Constant(0, typeof(int))); 
    var line3 = Expression.GreaterThan(Expression.Property(CS1, typeof(Stock).GetProperty("Price")), Expression.Constant((double)0, typeof(double))); 
    var line4 = Expression.And(line1,line2); 
    var line5 = Expression.OrElse(line4, line3); 

    func = Expression.Lambda<Func<Stock, bool>>(line5, new ParameterExpression[] { CS1 }).Compile(); 
} 


public static void DynamicLambda(List<Stock> stks) 
{ 
    Stopwatch watch = new Stopwatch(); 
    watch.Start(); 
    foreach (var d in stks) 
    { 
     func(d); 
    } 
    watch.Stop(); 
    Console.WriteLine("Dynamic Lambda :" + watch.ElapsedMilliseconds); 
} 

public static void CompileTimeLamda(List<Stock> stks) 
{ 
    Stopwatch watch = new Stopwatch(); 
    watch.Start(); 
    foreach (var d in stks) 
    { 
     if (d.Symbol == "MSFT" && d.CloseDate.Millisecond > 0 || 
            (d.Price) > 0) ; 
    } 
    watch.Stop(); 
    Console.WriteLine("Compile Time Lamda " +watch.ElapsedMilliseconds); 
} 
+3

コンパイル済みのILを確認してください。オプティマイザがすべてのコードを強制終了している可能性があります。 – SLaks

+0

2つのラムダ式を実際に比較していません。 2番目のコードはコンパイルされたコードです(つまり、代理人はいません)。デリゲートが遅くなっている可能性は非常に高いです。 – MikeKulls

+0

あなたはリンゴとオレンジを比較しているようです。 "compile time lambda"はラムダをまったく使用しません。さらに、コンパイラは実際には何もしていないのでループを最適化しています(空のステートメント ";") –

答えて

2

違いは、コンパイラはラムダを使用して、また...より多くの情報を持って、あなたではなく、実行時に比べて、コンパイル時に、それをコンパイルした場合、コードの最適化に多くの努力を費やして行う必要があり、あなたが持っていますより柔軟なプログラム(実行時にラムダを選択できます)。これは、追加の関数呼び出しがあり、多くの潜在的な最適化が失われているので、 というコストがかかります。あなたもよあり

..代わりにハードコードされたコードの

Func<Stock, bool> compileTime = (Stock d) => (d.Symbol == "MSFT" && d.CloseDate.Millisecond > 0) || d.Price > 0; 

はあなたのようなものを使用して動的ラムダ対静的ラムダを比較することができ、より多くの「公平」な比較を行うために、違いはあるが、わずかに小さいもの...違いは同じ理由(より多くの最適化)...ラムダを手作業で最適化することで差を減らすことができます(コンパイラはラムダで手動で作成できない有効なCLIコードを作成します)。

しかし、たとえば、あなたからあなたの動的なラムダを変更した場合:に

var line5 = Expression.OrElse(line4, line3); 

var line5 = Expression.OrElse(line3, line4); 

あなたは、ラムダが1倍と元のコンパイルされたコードの2倍の間で実行する方法がわかります。

5

ラムダ式、コンパイルされた式ツリー、ストレート関数呼び出し、インラインコードを比較して自分でテストしました。結果は非常に興味深いものでした。私は、表現木が速かったので、私のテストでは欠陥がほとんどあると思っていますが、これは不可能ではないと思います。ラムダ式は最も遅いです!興味深いのは、式ツリーが関数呼び出しよりも速く、インラインコードよりわずかに遅いことです。私が期待したものではありません。

編集:実は、私は3回のランの

void TestIt() 
    { 
     var ints = new int[10000000]; 
     Random rand = new Random(); 
     for (int i = 0; i < ints.Length; i++) 
      ints[i] = rand.Next(100); 

     Func<int, int> func1 = i => i + 2; 
     Func<int, int> func2 = CompileIt(); 

     var stopwatch = new Stopwatch(); 

     for (int x = 0; x < 3; x++) 
     { 
      stopwatch.Restart(); 
      for (int i = 0; i < ints.Length; i++) 
       ints[i] = func1(ints[i]); 
      stopwatch.Stop(); 
      Console.Write("Lamba      "); 
      Console.Write(stopwatch.ElapsedMilliseconds); 
      ShowSum(ints); 

      stopwatch.Restart(); 
      for (int i = 0; i < ints.Length; i++) 
       ints[i] = func2(ints[i]); 
      stopwatch.Stop(); 
      Console.Write("Lambda from expression tree "); 
      Console.Write(stopwatch.ElapsedMilliseconds); 
      ShowSum(ints); 

      stopwatch.Restart(); 
      for (int i = 0; i < ints.Length; i++) 
       ints[i] = AddTwo(ints[i]); 
      stopwatch.Stop(); 
      Console.Write("Compiled function   "); 
      Console.Write(stopwatch.ElapsedMilliseconds); 
      ShowSum(ints); 

      stopwatch.Restart(); 
      for (int i = 0; i < ints.Length; i++) 
       ints[i] = ints[i] + 2; 
      stopwatch.Stop(); 
      Console.Write("Compiled code    "); 
      Console.Write(stopwatch.ElapsedMilliseconds); 
      ShowSum(ints); 
     } 
    } 

    private int AddTwo(int value) 
    { 
     return value + 2; 
    } 

    private void ShowSum(int[] ints) 
    { 
     Console.WriteLine(" Sum = " + ints.Sum(i => i).ToString()); 
    } 

    private Func<int, int> CompileIt() 
    { 
     var param1 = Expression.Parameter(typeof(int)); 
     Expression body = Expression.Add(param1, Expression.Constant(2)); 
     return Expression.Lambda<Func<int, int>>(body, new [] { param1 }).Compile(); 
    } 

結果、以下の結果にスピードに等しくなるように、ラムダとコンパイルされた関数を検討する、次のとおりです。

Lamba      164 Sum = 515074919 
Lambda from expression tree 86 Sum = 535074919 
Compiled function   155 Sum = 555074919 
Compiled code    54 Sum = 575074919 

Lamba      153 Sum = 595074919 
Lambda from expression tree 88 Sum = 615074919 
Compiled function   156 Sum = 635074919 
Compiled code    53 Sum = 655074919 

Lamba      156 Sum = 675074919 
Lambda from expression tree 88 Sum = 695074919 
Compiled function   157 Sum = 715074919 
Compiled code    54 Sum = 735074919 
関連する問題