Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is IL generated by expression trees optimized?

Ok this is merely curiosity, serves no real world help.

I know that with expression trees you can generate MSIL on the fly just like the regular C# compiler does. Since compiler can decide optimizations, I'm tempted to ask what is the case with IL generated during Expression.Compile(). Basically two questions:

  1. Since at compile time the compiler can produce different (may be slightly) IL in debug mode and release mode, is there ever a difference in the IL generated by compiling an expression when built in debug mode and release mode?

  2. Also JIT which convert IL to native code at run time should be vastly different in both debug mode and release mode. Is this also the case with compiled expressions? Or are IL from expression trees not jitted at all?

My understanding could be flawed, correct me in case.

Note: I'm considering the cases where the debugger is detached. I'm asking about the default configuration setting that comes with "debug" and "release" in visual studio.

like image 763
nawfal Avatar asked Oct 14 '13 07:10

nawfal


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 query expression trees?

An expression tree is a representation of expressions arranged in a tree-like data structure. In other words, it is a tree with leaves as operands of the expression and nodes contain the operators. Similar to other data structures, data interaction is also possible in an expression tree.


1 Answers

Since at compile time the compiler can produce different (may be slightly) IL in debug mode and release mode, is there ever a difference in the IL generated by compiling an expression when built in debug mode and release mode?

This one actually has a very simple answer: no. Given two identical LINQ/DLR expression trees, there will be no difference in the generated IL if one is compiled by an application running in Release mode, and the other in Debug mode. I'm not sure how that would be implemented anyway; I don't know of any reliable way for code within System.Core to know that your project is running a debug build or release build.

This answer may actually be misleading, however. The IL emitted by the expression compiler may not differ between debug and release builds, but in cases where expression trees are emitted by the C# compiler, it is possible that the structure of the expression trees themselves may differ between debug and release modes. I am fairly well acquainted with the LINQ/DLR internals, but not so much with the C# compiler, so I can only say that there may be a difference in those cases (and there may not).

Also JIT which convert IL to native code at run time should be vastly different in both debug mode and release mode. Is this also the case with compiled expressions? Or are IL from expression trees not jitted at all?

The machine code that the JIT compiler spits out will not necessarily be vastly different for pre-optimized IL versus unoptimized IL. The results may well be identical, particularly if the only differences are a few extra temporary values. I suspect the two will diverge more in larger and more complex methods, as there is usually an upper limit to the time/effort the JIT will spend optimizing a given method. But it sounds like you are more interested in how the quality of compiled LINQ/DLR expression trees compares to, say, C# code compiled in debug or release mode.

I can tell you that the LINQ/DLR LambdaCompiler performs very few optimizations--fewer than the C# compiler in Release mode for sure; Debug mode may be closer, but I would put my money on the C# compiler being slightly more aggressive. The LambdaCompiler generally does not attempt to reduce the use of temporary locals, and operations like conditionals, comparisons, and type conversions will typically use more intermediate locals than you might expect. I can actually only think of three optimizations that it does perform:

  1. Nested lambdas will be inlined when possible (and "when possible" tends to be "most of the time"). This can help a lot, actually. Note, this only works when you Invoke a LambdaExpression; it does not apply if you invoke a compiled delegate within your expression.

  2. Unnecessary/redundant type conversions are omitted, at least in some cases.

  3. If the value of a TypeBinaryExpression (i.e., [value] is [Type]) is known at compile time, that value may be inlined as a constant.

Apart from #3, the expression compiler does no "expression-based" optimizations; that is, it will not analyze the expression tree looking for optimization opportunities. The other optimizations in the list occur with little or no context about other expressions in the tree.

Generally, you should assume that the IL resulting from a compiled LINQ/DLR expression is considerably less optimized than the IL produced by the C# compiler. However, the resulting IL code is eligible for JIT optimization, so it is difficult to assess the real world performance impact unless you actually try to measure it with equivalent code.

One of the things to keep in mind when composing code with expression trees is that, in effect, you are the compiler1. LINQ/DLR trees are designed to be emitted by some other compiler infrastructure, like the various DLR language implementations. It's therefore up to you to handle optimizations at the expression level. If you are a sloppy compiler and emit a bunch of unnecessary or redundant code, the generated IL will be larger and less likely to be aggressively optimized by the JIT compiler. So be mindful of the expressions you construct, but don't fret too much. If you need highly optimized IL, you should probably just emit it yourself. But in most cases, LINQ/DLR trees perform just fine.


1 If you have ever wondered why LINQ/DLR expressions are so pedantic about requiring exact type matching, it's because they are intended to serve as a compiler target for multiple languages, each of which may have different rules regarding method binding, implicit and explicit type conversions, etc. Therefore, when constructing LINQ/DLR trees manually, you must do the work that a compiler would normally do behind the scenes, like automatically inserting code for implicit conversions.

like image 61
Mike Strobel Avatar answered Oct 22 '22 19:10

Mike Strobel