我在应用程序中观察到许多“堆栈内省”代码,这些代码通常隐式地依赖于其包含方法未被内联以确保其正确性。此类方法通常涉及调用:
-
MethodBase.GetCurrentMethod
-
Assembly.GetCallingAssembly
-
Assembly.GetExecutingAssembly
现在,我发现围绕这些方法的信息非常混乱。我听说运行时不会内联调用 GetCurrentMethod 的方法,但我找不到任何相关文档。我曾多次在 StackOverflow 上看到过帖子,例如这个,表示 CLR 不内联跨程序集调用,而是使用 GetCallingAssembly
文档 强烈表明并非如此。
还有备受诟病的 [MethodImpl(MethodImplOptions.NoInlined)]
,但我不确定 CLR 是否认为这是“请求”或“命令”。
请注意,我从合同的角度询问内联资格,不是关于 JITter 的当前实现何时由于实现困难而拒绝考虑方法,或者关于 JITter 何时在评估权衡后,最终选择内联一个合格的方法。我已阅读此和这个,但他们似乎更关注最后两个点(顺便提到了 MethodImpOptions.NoInlined 和“异国情调的 IL 指令”,但这些似乎是作为启发式方法而不是义务来呈现的)。
什么时候允许 CLR 内联?
I've observed a lot of "stack-introspective" code in applications, which often implicitly rely on their containing methods not being inlined for their correctness. Such methods commonly involve calls to:
MethodBase.GetCurrentMethod
Assembly.GetCallingAssembly
Assembly.GetExecutingAssembly
Now, I find the information surrounding these methods to be very confusing. I've heard that the run-time will not inline a method that calls GetCurrentMethod, but I can't find any documentation to that effect. I've seen posts on StackOverflow on several occasions, such as this one, indicating the CLR does not inline cross-assembly calls, but the GetCallingAssembly
documentation strongly indicates otherwise.
There's also the much-maligned [MethodImpl(MethodImplOptions.NoInlining)]
, but I am unsure if the CLR considers this to be a "request" or a "command."
Note that I am asking about inlining eligibility from the standpoint of contract, not about when current implementations of the JITter decline to consider methods because of implementation difficulties, or about when the JITter finally ends up choosing to inline an eligible method after assessing the trade-offs. I have read this and this, but they seem to be more focused on the last two points (there are passing mentions of MethodImpOptions.NoInlining and "exotic IL instructions", but these seem to be presented as heuristics rather than as obligations).
When is the CLR allowed to inline?
发布评论
评论(5)
这是抖动实现细节,x86 和 x64 抖动的规则略有不同。这在致力于抖动的团队成员的博客文章中被随意记录,但团队当然保留更改规则的权利。看起来你已经找到它们了。
来自其他程序集的内联方法肯定是受支持的,如果不是这种情况,许多 .NET 类将工作得非常糟糕。当您查看为 Console.WriteLine() 生成的机器代码时,您可以看到它的工作原理,当您传递一个简单的字符串时,它通常会被内联。要亲眼看到这一点,您需要切换到发布版本并更改调试器选项。工具>选项>调试>一般情况下,取消选中“抑制模块加载时的 JIT 优化”。
否则没有充分的理由认为 MethodImpOptions.NoInlined 受到诽谤,这几乎就是它首先存在的原因。事实上,它在 .NET 框架中有意用于许多调用内部辅助方法的小型公共方法。它使异常堆栈跟踪更容易诊断。
It is a jitter implementation detail, the x86 and x64 jitters have subtly different rules. This is casually documented in blog posts of team members that worked on the jitter but the teams certainly reserve the right to alter the rules. Looks like you already found them.
Inlining methods from other assemblies is most certainly supported, a lot of the .NET classes would work quite miserably if that wasn't the case. You can see it at work when you look at the machine code generated for Console.WriteLine(), it often gets inlined when you pass a simple string. To see this for yourself, you need to switch to the Release build and change a debugger option. Tools > Options > Debugging > General, untick "Suppress JIT optimization on module load".
There is otherwise no good reason to consider MethodImpOptions.NoInlining maligned, it's pretty much why it exists in the first place. It is in fact used intentionally in the .NET framework on lots of small public methods that call an internal helper method. It makes exception stack traces easier to diagnose.
尽管汉斯·帕桑特的回答,这里首先是 2004 年的一些提示,然后是一些更多的最新信息。它们可能会发生变化,但如果您想让一个方法符合内联条件,它们确实可以让您了解要寻找什么:
特别是,有 MethodImplOptions.AggressiveInlined ,这应该会解除 32 字节的限制(或者现在和您的平台上发生的任何情况)。
.Net 3.5 添加了启发式方法,帮助其确定是否 内联还是不内联,这可能是一件好事,尽管它使开发人员更难预测抖动的决定:
引用自文章:
Hans Passant's answer notwithstanding, here first a couple of hints as of 2004, and further down some more up to date information. They are subject to change, but they do give you an idea on what to look for if you want to make a method eligible for inlining:
In particular, there is MethodImplOptions.AggressiveInlining, which is supposed to lift the 32 bytes limit (or whatever it happens to be these days and for your platform).
.Net 3.5 added heuristics that help it determine whether To Inline or not to Inline, which is probably a good thing, although it makes it harder for the developer to predict the jitter's decision:
A quote from the article:
而汉斯的回答 是正确的,但有一个遗漏,不一定是关于方法何时适合内联,而是关于方法何时不适合内联。
抽象方法和虚拟方法不适合在 CLR 中内联。
值得注意的是,它减少了方法内联的条件。可能。
While Hans' answer is correct, there is one omission, not necessarily about when a method is eligible for inlining, but when a method is not.
Abstract and virtual methods are not eligible for inlining in the CLR.
It's important to note as it whittles down the conditions under which a method may be inlined.
有关此线程上 MethodBase.GetCurrentMethod 内联的更多信息 http://prdlxvm0001 .codify.net/pipermail/ozdotnet/2011-March/009085.html
大量解释说,RefCrawlMark 不会停止内联调用方法。然而,RequireSecObject 确实有阻止调用者内联的副作用。
此外,Assembly.GetCallingAssembly 和 Assembly.GetExecutingAssembly 方法没有此属性。
There's more information on inlining of MethodBase.GetCurrentMethod on this thread http://prdlxvm0001.codify.net/pipermail/ozdotnet/2011-March/009085.html
Paraphrasing heavily, it states that the RefCrawlMark does NOT stop the calling method being inlined. However, RequireSecObject does have the side affect of stopping the caller being inlined.
In addition, the Assembly.GetCallingAssembly and Assembly.GetExecutingAssembly methods do NOT have this attribute.
2003 年,MSDN 上发布了一篇文章,名为 编写高性能托管应用程序非常清楚地涵盖了几个标准:
Sacha Goldshtein 于 2012 年发表的博客文章 CLR 中的激进内联 有很多相同的建议。
There was an article posted on MSDN in 2003 called Writing High-Performance Managed Apps that covers the outlines several criteria quite clearly:
Sacha Goldshtein's blog article in 2012 on aggressive inlining in the CLR has much of the same advice.