37

我在应用程序中观察到很多“堆栈内省”代码,这些代码通常隐含地依赖于它们的包含方法,而不是为了它们的正确性而内联。此类方法通常涉及调用:

  • MethodBase.GetCurrentMethod
  • Assembly.GetCallingAssembly
  • Assembly.GetExecutingAssembly

现在,我发现围绕这些方法的信息非常混乱。我听说运行时不会内联调用 GetCurrentMethod 的方法,但我找不到任何相关文档。我曾多次在 StackOverflow 上看到过帖子,例如这个,表明 CLR 没有内联跨汇编调用,但GetCallingAssembly 文档强烈表明并非如此。

还有饱受诟病的[MethodImpl(MethodImplOptions.NoInlining)],但我不确定 CLR 是否认为这是“请求”或“命令”。

请注意,我从合同的角度询问内联资格,而不是关于 JITter 的当前实现何时因实施困难而拒绝考虑方法,或者 JITter在评估交易后最终最终选择内联合格方法的时间 -关闭。我读过这个这个,但他们似乎更关注最后两点(有提到 MethodImpOptions.NoInlining 和“exotic IL instructions”,但这些似乎是作为启发式而不是作为义务提出的)。

什么时候允许CLR内联?

4

5 回答 5

23

这是一个抖动实现细节,x86 和 x64 抖动有细微的不同规则。这在处理抖动的团队成员的博客文章中随意记录,但团队当然保留更改规则的权利。看起来你已经找到它们了。

肯定支持来自其他程序集的内联方法,如果不是这种情况,许多 .NET 类的工作将非常糟糕。当您查看为 Console.WriteLine() 生成的机器代码时,您可以看到它在工作,当您传递一个简单的字符串时,它通常会被内联。要亲自查看此内容,您需要切换到 Release 版本并更改调试器选项。工具 + 选项,调试,常规,取消勾选“在模块加载时抑制 JIT 优化”。

否则没有充分的理由考虑 MethodImpOptions.NoInlining 诽谤,这就是它首先存在的原因。事实上,它是有意在 .NET 框架中用于调用内部辅助方法的许多小型公共方法上的。它使异常堆栈跟踪更容易诊断。

于 2011-01-11T18:06:38.003 回答
7

尽管有 Hans Passant 的回答,但这里首先是 2004 年的一些提示,然后是一些更多最新信息。它们可能会发生变化,但如果你想让一个方法符合内联条件,它们确实让你知道要寻找什么:

JIT 不会内联:

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

特别是,有MethodImplOptions.AggressiveInlining,它应该解除 32 字节的限制(或者这些天和你的平台发生的任何事情)。

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

引用文章:

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

  2. 一个特定的调用站点执行得越多,它就越能从内联中受益。因此,循环中的代码比非循环中的代码更值得内联。

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

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

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

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

  3. 计算一个乘数。默认为 1

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

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

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

于 2015-07-17T01:14:06.470 回答
3

虽然Hans 的回答是正确的,但有一个遗漏,不一定是关于方法何时符合内联条件,而是方法何时不符合条件。

抽象和虚拟方法不适合在 CLR 中内联

重要的是要注意,因为它减少了可以内联方法的条件

于 2011-01-26T00:24:43.363 回答
1

在这个线程上有更多关于 MethodBase.GetCurrentMethod 内联的信息http://prdlxvm0001.codify.net/pipermail/ozdotnet/2011-March/009085.html

重述一下,它指出 RefCrawlMark 不会停止内联调用方法。但是,RequireSecObject 确实具有停止内联调用者的副作用。

此外,Assembly.GetCallingAssembly 和 Assembly.GetExecutingAssembly 方法没有此属性。

于 2012-06-09T00:03:30.707 回答
0

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

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

Sacha Goldshtein 在 2012 年关于CLR 中的激进内联的博客文章有很多相同的建议。

于 2019-03-30T13:52:09.703 回答