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?
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".
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.
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.
Compile() Compiles the lambda expression described by the expression tree into executable code and produces a delegate that represents the lambda expression.
Compilation
The call to Expression.Compile
goes through exactly the same process as any other .NET code your application contains in the sense that:
(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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With