Performance of expression trees

cs0815 picture cs0815 · Jul 17, 2014 · Viewed 9.8k times · Source

My current understanding is that 'hard coded' code like this:

public int Add(int x, int y) {return x + y;}

will always perform better than expression tree code like this:

Expression<Func<int, int, int>> add = (x, y) => x + y; 
var result = add.Compile()(2, 3);

var x = Expression.Parameter(typeof(int)); 
var y = Expression.Parameter(typeof(int)); 
return (Expression.Lambda(Expression.Add(x, y), x, y).
    Compile() as Func<int, int, int>)(2, 3);

as the compiler has more information and can spend more effort on optimizing the code if you compile it at compile time. Is this generally true?

Answer

Bas picture Bas · Jul 17, 2014

Compilation

The call to Expression.Compile goes through exactly the same process as any other .NET code your application contains in the sense that:

  • IL code is generated
  • IL code is JIT-ted to machine code

(the parsing step is skipped because an Expression Tree is already created and does not have to be generated from the input code)

You can look at the source code of the expression compiler to verify that indeed, IL code is generated.

Optimization

Please be aware that almost all of the optimization done by the CLR is done in the JIT step, not from compiling C# source code. This optimization will also be done when compiling the IL code from your lambda delegate to machine code.

Your example

In your example you are comparing apples & oranges. The first example is a method definition, the second example is runtime code that creates a method, compiles and executes it. The time it takes to create/compile the method is much longer than actually executing it. However you can keep an instance of the compiled method after creation. When you have done that, the performance of your generated method should be identical to that of the original C# method.

Consider this case:

private static int AddMethod(int a, int b)
{
    return a + b;
}

Func<int, int, int> add1 = (a, b) => a + b;
Func<int, int, int> add2 = AddMethod;

var x = Expression.Parameter(typeof (int));
var y = Expression.Parameter(typeof (int));
var additionExpr = Expression.Add(x, y);
Func<int, int, int> add3 = 
              Expression.Lambda<Func<int, int, int>>(
                  additionExpr, x, y).Compile();
//the above steps cost a lot of time, relatively.

//performance of these three should be identical
add1(1, 2);
add2(1, 2);
add3(1, 2);

So the conclusion one might draw is: IL code is IL code, no matter how it is generated, and Linq Expressions generate IL code.