Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance of expression trees

Tags:

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?

like image 655
cs0815 Avatar asked Jul 17 '14 11:07

cs0815


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 expression trees used for?

Expression Trees represent code as a structure that you can examine, modify, or execute. These tools give you the power to manipulate code during run time. You can write code that examines running algorithms, or injects new capabilities.

What is more complex expression tree?

That's the basics of building an expression tree in memory. More complex trees generally mean more node types, and more nodes in the tree. Let's run through one more example and show two more node types that you will typically build when you create expression trees: the argument nodes, and method call nodes.

What is compiled expression in C#?

Compile() Compiles the lambda expression described by the expression tree into executable code and produces a delegate that represents the lambda expression.


2 Answers

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.

like image 51
Bas Avatar answered Sep 23 '22 05:09

Bas


Your Add function probably compiles to some function overhead (if not inlined) and a single add instruction. Doesn't get any faster than that.

Even constructing this expression tree is going to be orders of magnitude slower. Compiling a new function for each invocation is going to be incredibly expensive compared to the direct C# implementation.

Try compiling the function just once and storing it somewhere.

like image 24
usr Avatar answered Sep 24 '22 05:09

usr