【问题标题】:When is a method eligible to be inlined by the CLR?什么时候有资格被 CLR 内联的方法?
【发布时间】:2011-06-07 07:07:08
【问题描述】:

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

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

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

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

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

CLR 何时允许内联?

【问题讨论】:

  • "什么时候允许 CLR 内联?"我猜只要该属性不存在。
  • @CodeInChaos:所以你是说Assembly.GetExecutingAssembly 被破坏了,除非我们用[MethodImpl(MethodImpOptions.NoInlining)] 装饰方法?
  • 这是一个很好的链接(不能回答你所有的问题......):blogs.msdn.com/b/vancem/archive/2008/08/19/…
  • 嗯,听起来像一个规范错误:我理解这样做的技术上的便利性,但显然 GetCallingAssembly 和朋友永远不应该被指定为内联敏感......叹息 - 和 GetExecutingAssembly 的文档不根本不提内联!
  • 不是您问题的答案,但从 Visual Studio 2012 开始,现在可以在编译器的帮助下使用 Caller Information 属性获取调用方方法/属性名称、其文件位置和行号,即不受 JIT 内联的影响。此外,与堆栈(帧创建和后续)检查相比,它的速度非常快。

标签: .net clr jit inlining


【解决方案1】:

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

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

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

【讨论】:

  • 从你说的我能推断出void M()``{Console.WriteLine(MethodBase.GetCurrentMethod().Name);}保证输出M保证输出@987654323 @ 在我用NoInlining 装饰它之后? Assembly.GetCallingAssembly 和 Assembly.GetExecutingAssembly 怎么样?尽管存在“此程序集”意图,但我很少看到调用 Assembly.GetExecutingAssembly 的代码被该属性修饰。
  • 没错。是的,包含此类代码的方法如果是公共的,则应使用该属性进行修饰。由于方法的大小,它可能会幸存下来。
【解决方案2】:

尽管有 Hans Passant 的回答,here 首先是 2004 年的一些提示,然后是更多最新信息。它们可能会发生变化,但如果您想让方法符合内联条件,它们确实可以让您了解要查找的内容:

JIT 不会内联:

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

特别是MethodImplOptions.AggressiveInlining,它应该会解除 32 字节的限制(或者最近发生的任何事情,并且适用于您的平台)。

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

引用文章:

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

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

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

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

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

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

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

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

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

  6. 如果 InlineSize

【讨论】:

    【解决方案3】:

    虽然Hans' answer 是正确的,但有一个遗漏,不一定是关于一个方法何时可以内联,而是当一个方法不是

    Abstract and virtual methods are not eligible for inlining in the CLR.

    注意这一点很重要,因为它减少了方法可能内联的条件。

    【讨论】:

    • 我不同意;这是一个当前为真的实现细节。 [“我们可能在这方面做得更好(例如,如果 99% 的调用最终都在同一个目标中,您可以生成代码来检查虚拟调用将在其上执行的对象的方法表,如果它是不是 99% 的情况,你打电话,否则你只执行内联代码)"]。 blogs.msdn.com/b/davidnotario/archive/2004/11/01/250398.aspx
    • 随机发现了这个 - 并发现链接回我的问题。 :-)
    【解决方案4】:

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

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

    另外,Assembly.GetCallingAssembly 和 Assembly.GetExecutingAssembly 方法没有这个属性。

    【讨论】:

      【解决方案5】:

      2003 年在 MSDN 上发布了一篇名为 Writing High-Performance Managed Apps 的文章,其中非常清楚地概括了几个标准:

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

      Sacha Goldshtein 2012 年在aggressive inlining in the CLR 上的博客文章也有很多相同的建议。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-28
        • 2010-12-18
        相关资源
        最近更新 更多