Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance of Compiled Expression Tree

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);
}
like image 862
GammaVega Avatar asked Jul 26 '12 20:07

GammaVega


People also ask

Are expression trees slow?

Expression tree compilation for example has a built in interpreter so it still "works". The problem is that its often slower than just using reflection, especially since Native AOT has "faster reflection".

What are the advantages of expression tree?

Expression trees allow you to build code dynamically at runtime instead of statically typing it in the IDE and using a compiler. They are well explained in the documentation.

What is compiled expression in C#?

The Compile method can be used to obtain the value of any expression tree. First, create a lambda expression that has the expression as its body by using the Lambda method. Then call Compile to obtain a delegate, and execute the delegate to obtain the value of the expression.

What is the use of expression tree?

Expression Trees provide richer interaction with the arguments that are functions. You write function arguments, typically using Lambda Expressions, when you create LINQ queries. In a typical LINQ query, those function arguments are transformed into a delegate the compiler creates.


2 Answers

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
like image 102
MikeKulls Avatar answered Nov 05 '22 19:11

MikeKulls


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.

like image 35
user1494736 Avatar answered Nov 05 '22 20:11

user1494736