表达树生成的IL是否经过优化? [英] Is IL generated by expression trees optimized?

查看:60
本文介绍了表达树生成的IL是否经过优化?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

好吧,这仅仅是好奇心,对现实世界没有任何帮助。



我知道,有了表达式树,您可以像常规C#编译器一样即时生成MSIL。 。由于编译器可以决定优化,因此我很想问在 Expression.Compile()期间生成IL的情况。基本上有两个问题:


  1. 由于在编译时,编译器可以在 debug模式下生成不同的IL(可能略有不同)和发布模式,在调试模式和发布模式下编译时,通过编译表达式生成的IL是否有区别?


  2. 也可以进行JIT在调试模式和发布模式下,在运行时将IL转换为本地代码的方式应该有很大的不同。编译表达式也是如此吗?还是表达树中的IL根本不会被激怒?


我的理解可能有缺陷,以防万一。 / p>

注意:我正在考虑分离调试器的情况。我问的是Visual Studio中调试和发布随附的默认配置设置。

解决方案


由于在编译时,编译器可以在调试模式和释放模式下生成不同的IL(可能略有不同),因此在调试模式和释放模式下编译表达式时生成的IL是否存在差异? / p>

这个实际上有一个非常简单的答案:不。给定两个相同的LINQ / DLR表达式树,如果一个由在Release模式下运行的应用程序编译,而另一个在Debug模式下运行,则生成的IL将没有差异。我不确定该如何实施;我不知道在 System.Core 中使用任何可靠的方式来知道您的项目正在运行调试版本还是发行版本。



这个答案实际上可能是误导的。表达式编译器发出的IL在调试和发行版本之间可能不会有所不同,但是在C#编译器发出表达式树的情况下,表达式树本身的结构在调试和发行模式之间可能会有所不同。我对LINQ / DLR内部非常熟悉,但是对C#编译器却不太了解,因此我只能说在这些情况下 可能有所不同(也许没有)。 / p>


在运行时将IL转换为本地代码的JIT在调试模式和发布模式下也应有很大的不同。编译表达式也是如此吗?还是表达式树中的IL根本不被抖动?


JIT编译器吐出的机器代码不一定是预优化的IL与未优化的IL有很大的差异。结果可能完全相同,特别是如果唯一的区别是几个额外的临时值。我怀疑这两种方法在更大和更复杂的方法上会有更大的差异,因为JIT通常花费时间/精力来优化给定方法。但是,听起来您对如何将LINQ / DLR表达式树的编译质量与以调试或发布模式编译的C#代码进行比较感兴趣。



我可以告诉您LINQ / DLR LambdaCompiler 执行的优化很少-肯定比发布模式下的C#编译器少;调试模式可能更接近一些,但是我会花钱在更具侵略性的C#编译器上。 LambdaCompiler 通常不会尝试减少对临时本地语言的使用,而条件,比较和类型转换等操作通常会使用比预期更多的中间本地语言。我实际上只能想到它会执行的三个优化:


  1. 嵌套的lambda将被内联在可能的情况下(而在可能的情况下往往是大部分时间)。实际上,这可以有很大帮助。请注意,这仅在 LambdaExpression 调用时有效。


  2. 至少在某些情况下会省略不必要/多余的类型转换。


  3. 如果 TypeBinaryExpression 的值(即 [value]为[Type] )在编译时是已知的,该值可以内联为常量。


从#3开始,表达式编译器不执行基于表达式的优化;也就是说,它不会分析表达式树以寻找优化机会。列表中的其他优化几乎没有树中其他表达式的上下文。



通常,您应该假定由编译的LINQ / DLR表达式产生的IL与C#编译器产生的IL相比,其优化程度要差很多。但是,生成的IL代码有资格进行JIT优化,因此,除非您实际尝试使用等效代码来衡量它,否则很难评估对现实世界的性能影响。



用表达式树编写代码时要记住的一件事是,实际上, you 是编译器 1 。 LINQ / DLR树被设计为由其他一些编译器基础结构发出,例如各种DLR语言实现。因此,需要在表达式级别进行优化。如果您是一个草率的编译器,并且发出一堆不必要的或冗余的代码,则生成的IL将更大,并且不太可能被JIT编译器积极地优化。因此,请注意构建的表达式,但不要过多担心。如果您需要高度优化的IL,则可能应该自己发出它。但是在大多数情况下,LINQ / DLR树的性能都很好。






1 如果您曾经想知道为什么LINQ / DLR表达式为何如此需要精确的类型匹配,那是因为它们旨在用作多种语言的编译器目标,每种语言在方法绑定,隐式和显式类型转换方面可能有不同的规则因此,在手动构造LINQ / DLR树时,必须完成编译器通常会在幕后完成的工作,例如自动为隐式转换插入代码。


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.

解决方案

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.

这篇关于表达树生成的IL是否经过优化?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆