Performance of Compiled Expression Tree
Asked Answered
L

3

7

I have a simple scenario, where i am trying to test the performance of a expression compiled tree on an list of stock objects. Below is the code

The performance of expression compiled tree is 5x slower than static lambda call. I am not sure whether this is a standard performance one can expect with expression compiled tree. Would appreciate any insight.

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);
}
Leisured answered 26/7, 2012 at 20:59 Comment(3)
Check the compiled IL; the optimizer may be killing all of your code.Impostume
You are not actually comparing 2 lambda expressions. The second is just compiled code, ie no delegate. It's quite possible the delegate is what is making it slower.Concertina
You appear to be comparing apples and oranges. The "compile time lambda" doesn't use a lambda at all. Plus, the compiler is probably optimizing away the loop because you're not really doing anything (empty statement ";")Rochette
C
3

The difference has to do with the compiler having more information and spending more effort on optimizing the code if you compile it at compile time, rather than at runtime... Also, using a lambda, you have a more "flexible" program (you can choose the lambda at run time). That comes at the cost of an extra function call, and losing a lot of potential optimizations.

To make a more "fair" comparison, you can compare a static lambda vs a dynamic lambda using something like:

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

instead of the hardcoded code..

There you'll also find a difference, but a slightly smaller one... The difference is for the same reason (more optimization)... You can decrease the difference by optimizing your lambda by hand (although that isn't always possible, since the compiler can create valid CLI code that can't be created manually with a lambda).

But for example, if you change your dynamic lambda from:

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

to:

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

You'll see how the lambda performs between 1x and 2x of your original compiled code.

Caliphate answered 26/7, 2012 at 21:37 Comment(0)
C
7

I did a bit of testing myself comparing lambda expression, compiled expression tree, straight function call and inline code. The results were very interesting. I almost think there is a fault in my test because the expression tree was faster but I guess this is not impossible. The lambda expression is the slowest!! The interesting thing is that the expression tree is quicker than the function call and is only slightly slower than inline code. Not what I expected at all.

Edit: Actually I would consider the lambda and compiled function to be equal in speed in the results below

    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();
    }

Results for 3 runs are:

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
Concertina answered 26/7, 2012 at 22:23 Comment(0)
C
3

The difference has to do with the compiler having more information and spending more effort on optimizing the code if you compile it at compile time, rather than at runtime... Also, using a lambda, you have a more "flexible" program (you can choose the lambda at run time). That comes at the cost of an extra function call, and losing a lot of potential optimizations.

To make a more "fair" comparison, you can compare a static lambda vs a dynamic lambda using something like:

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

instead of the hardcoded code..

There you'll also find a difference, but a slightly smaller one... The difference is for the same reason (more optimization)... You can decrease the difference by optimizing your lambda by hand (although that isn't always possible, since the compiler can create valid CLI code that can't be created manually with a lambda).

But for example, if you change your dynamic lambda from:

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

to:

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

You'll see how the lambda performs between 1x and 2x of your original compiled code.

Caliphate answered 26/7, 2012 at 21:37 Comment(0)
J
0

Here the alternative approach that utilizes manual visiting of expression and emitting the effective code.

Jeanett answered 24/11, 2015 at 11:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.