由于在编译时编译器可以在调试模式和发布模式下生成不同的(可能略有不同)IL,因此在调试模式和发布模式下构建时编译表达式生成的IL是否有差异?
这个问题实际上有一个非常简单的答案:不。给定两个相同的 LINQ/DLR 表达式树,如果其中一个由在发布模式下运行的应用程序编译,而另一个在调试模式下运行,则生成的 IL 不会有任何差异。无论如何,我不确定这将如何实施;我不知道其中的代码有任何可靠的方法System.Core
了解您的项目正在运行调试版本或发布版本。
然而,这个答案实际上可能具有误导性。表达式编译器发出的 IL 在调试和发布版本之间可能没有差异,但在 C# 编译器发出表达式树的情况下,表达式树本身的结构在调试和发布模式之间可能会有所不同。我对 LINQ/DLR 内部结构相当熟悉,但对 C# 编译器不太了解,所以我只能说may在这些情况下会有差异(也可能没有)。
此外,在运行时将 IL 转换为本机代码的 JIT 在调试模式和发布模式下应该有很大不同。编译表达式也是如此吗?或者表达式树中的 IL 根本没有被抖动?
JIT编译器吐出的机器码不一定是vastly预优化的 IL 与未优化的 IL 不同。结果很可能是相同的,特别是如果唯一的区别是一些额外的临时值。我怀疑两者在更大、更复杂的方法中会出现更大的分歧,因为 JIT 优化给定方法所花费的时间/精力通常有上限。但听起来您似乎更感兴趣的是编译的 LINQ/DLR 表达式树的质量与在调试或发布模式下编译的 C# 代码相比如何。
我可以告诉你 LINQ/DLRLambdaCompiler
执行的优化非常少——肯定比发布模式下的 C# 编译器少;调试模式可能更接近,但我认为 C# 编译器稍微更激进一些。这LambdaCompiler
通常不会尝试减少临时局部变量的使用,并且条件、比较和类型转换等操作通常会使用比您预期更多的中间局部变量。我实际上只能想到三个优化does履行:
嵌套的 lambda 表达式将在可能的情况下内联(“如果可能”往往是“大多数时候”)。实际上,这可以有很大帮助。请注意,这仅在您Invoke
a LambdaExpression
;如果您在表达式中调用已编译的委托,则它不适用。
至少在某些情况下,省略了不必要/冗余的类型转换。
如果a的值TypeBinaryExpression
(i.e., [value] is [Type]
) 在编译时已知,该值可以作为常量内联。
除了 #3 之外,表达式编译器不进行“基于表达式”的优化;也就是说,它不会分析表达式树来寻找优化机会。列表中的其他优化在很少或没有关于树中其他表达式的上下文的情况下进行。
通常,您应该假设编译的 LINQ/DLR 表达式生成的 IL 的优化程度远低于 C# 编译器生成的 IL。然而,生成的 IL 代码有资格进行 JIT 优化,因此很难评估现实世界的性能影响,除非您实际尝试使用等效代码来测量它。
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.