方法什么时候可以被 CLR 内联?

发布于 2024-10-11 04:10:09 字数 1176 浏览 5 评论 0 原文

我在应用程序中观察到许多“堆栈内省”代码,这些代码通常隐式地依赖于其包含方法被内联以确保其正确性。此类方法通常涉及调用:

  • 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

千紇 2024-10-18 04:10:09

这是抖动实现细节,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.

傾城如夢未必闌珊 2024-10-18 04:10:09

尽管汉斯·帕桑特的回答,这里首先是 2004 年的一些提示,然后是一些更多的最新信息。它们可能会发生变化,但如果您想让一个方法符合内联条件,它们确实可以让您了解要寻找什么:

JIT 不会内联:

  • 使用 MethodImplOptions.NoInlined 标记的方法
  • IL 大于 32 字节的方法
  • 虚拟方法
  • 采用大值类型作为参数的方法
  • MarshalByRef 类的方法
  • 具有复杂流程图的方法
  • 满足其他更奇特标准的方法

特别是,有 MethodImplOptions.AggressiveInlined ,这应该会解除 32 字节的限制(或者现在和您的平台上发生的任何情况)。

.Net 3.5 添加了启发式方法,帮助其确定是否 内联还是不内联,这可能是一件好事,尽管它使开发人员更难预测抖动的决定:

引用自文章:

  1. 如果内联使代码比它所替换的调用更小,那么它总是好的。请注意,我们谈论的是 NATIVE 代码大小,而不是
    IL 代码大小(可能有很大不同)。

  2. 特定调用站点执行的次数越多,它从 inlnning 中受益就越多。因此循环中的代码值得内联更多
    比不在循环中的代码。

  3. 如果内联公开了重要的优化,那么内联是更可取的。特别是具有值类型参数的方法
    由于这样的优化,比平常受益更多,因此
    倾向于内联这些方法是好的。

因此,X86 JIT 编译器使用的启发式是,给定一个内联
候选人。

  1. 如果方法未内联,则估计调用站点的大小。

  2. 估计内联调用站点的大小(这是基于 IL 的估计,我们采用一个简单的状态机(马尔可夫
    模型),使用大量实际数据创建来形成此估计器逻辑)

  3. 计算乘数。默认为1

  4. 如果代码处于循环中,则增加乘数(当前启发式将循环中的乘数提高到 5)

  5. 如果结构优化看起来会生效,请增加乘数。

  6. 如果 InlineSize <= NonInlineSize * Multiplier 进行内联。

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:

the JIT won’t inline:

  • Methods marked with MethodImplOptions.NoInlining
  • Methods larger than 32 bytes of IL
  • Virtual methods
  • Methods that take a large value type as a parameter
  • Methods on MarshalByRef classes
  • Methods with complicated flowgraphs
  • Methods meeting other, more exotic criteria

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:

  1. If inlining makes code smaller then the call it replaces, it is ALWAYS good. Note that we are talking about the NATIVE code size, not
    the IL code size (which can be quite different).

  2. The more a particular call site is executed, the more it will benefit from inlning. Thus code in loops deserves to be inlined more
    than code that is not in loops.

  3. If inlining exposes important optimizations, then inlining is more desirable. In particular methods with value types arguments
    benefit more than normal because of optimizations like this and thus
    having a bias to inline these methods is good.

Thus the heuristic the X86 JIT compiler uses is, given an inline
candidate.

  1. Estimate the size of the call site if the method were not inlined.

  2. Estimate the size of the call site if it were inlined (this is an estimate based on the IL, we employ a simple state machine (Markov
    Model), created using lots of real data to form this estimator logic)

  3. Compute a multiplier. By default it is 1

  4. Increase the multiplier if the code is in a loop (the current heuristic bumps it to 5 in a loop)

  5. Increase the multiplier if it looks like struct optimizations will kick in.

  6. If InlineSize <= NonInlineSize * Multiplier do the inlining.

站稳脚跟 2024-10-18 04:10:09

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.

━╋う一瞬間旳綻放 2024-10-18 04:10:09

有关此线程上 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.

紫南 2024-10-18 04:10:09

2003 年,MSDN 上发布了一篇文章,名为 编写高性能托管应用程序非常清楚地涵盖了几个标准:

  • 大于 32 字节 IL 的方法将不会被内联。
  • 虚函数不是内联的。
  • 具有复杂流程控制的方法将不会被内联。复杂流控制是除 if/then/else 之外的任何流控制;在这种情况下,请切换或 while。
  • 包含异常处理块的方法不会内联,但引发异常的方法仍然是内联的候选者。
  • 如果该方法的任何形式参数是结构,则该方法将不会被内联。

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:

  • Methods that are greater than 32 bytes of IL will not be inlined.
  • Virtual functions are not inlined.
  • Methods that have complex flow control will not be in-lined. Complex flow control is any flow control other than if/then/else; in this case, switch or while.
  • Methods that contain exception-handling blocks are not inlined, though methods that throw exceptions are still candidates for inlining.
  • If any of the method's formal arguments are structs, the method will not be inlined.

Sacha Goldshtein's blog article in 2012 on aggressive inlining in the CLR has much of the same advice.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文