【问题标题】:What are good heuristics for inlining functions?内联函数有哪些好的启发式方法?
【发布时间】:2011-01-08 23:17:53
【问题描述】:

考虑到您只是在尝试优化速度,在决定是否内联函数时有什么好的启发式方法?显然代码大小应该很重要,但是当(比如说)gcc 或 icc 确定是否内联函数调用时,是否还有其他通常使用的因素?该领域是否有任何重要的学术工作?

【问题讨论】:

  • @Mitch:当然,但我很好奇编译器如何决定是否内联。
  • 主要是我相信的指令数量...

标签: gcc gnu compiler-construction icc inlining


【解决方案1】:

维基百科有关于此的afew 段落,底部有一些链接:

  • 除了内存大小和缓存问题,another consideration is register pressure。从编译器的角度来看,“内联过程中添加的变量可能会消耗额外的寄存器,并且在寄存器压力已经很高的区域中,这可能会强制溢出,从而导致额外的 RAM 访问。”

使用 JIT 编译器和运行时类加载的语言还有其他权衡,因为虚拟方法不是静态已知的,但 JIT 可以收集运行时分析信息,例如方法调用频率:

  • Design, Implementation, and Evaluation of Optimizations in a Just-in-Time Compiler(针对 Java)讨论静态方法和动态加载类的方法内联及其对性能的改进。

  • Practicing JUDO: Java Under Dynamic Optimizations 声称他们的“内联策略基于代码大小和分析信息。如果方法入口的执行频率低于某个阈值,则该方法不会被内联,因为它被视为一个冷方法。为了避免代码爆炸,我们不会内联字节码大小超过 25 字节的方法......为了避免沿着深度调用链内联,当调用链上累积的内联字节码大小超过时内联停止40 个字节。”尽管它们具有运行时分析信息(方法调用频率),但它们仍然小心避免内联大型函数或函数链以防止膨胀。

A search on Google Scholar透露了一些论文,比如

A search on Google Books 揭示了相当多的书籍,其中包含有关各种上下文中的函数内联的论文或章节。

【讨论】:

    【解决方案2】:

    函数调用意味着一些额外的代码(函数序言,新堆栈框架设置的地方,以及函数尾声,它被清理的地方)。如果您的编译器发现函数代码与序言和尾声相比较小,它可以决定不值得进行实际调用,并将内联函数。

    我看到调用函数而不是内联函数的唯一好处是与大小相关。我猜想内联一个函数然后展开一个循环会导致大小显着增加。

    【讨论】:

    • 内联不会影响缓存,因此(如果您没有内联)函数代码将被放置在与“主”代码不同的缓存行上?
    【解决方案3】:

    据我所知,函数大小是编译器用来确定内联的唯一因素。但是,如果您进行配置文件引导优化 (PGO),我相信编译器能够使用其他变量,例如调用次数/调用设置时间。

    【讨论】:

      【解决方案4】:

      在 .NET 中主要是基于大小。以编译字节为单位测量父函数和子函数的大小。然后测量组合函数的大小。如果组合函数较小,则内联是个好主意。

      这样做的原因是可以将尽可能多的代码推送到 CPU 的缓存中。缓存未命中比现代 CPU 中的函数调用代价更高。

      【讨论】:

      • .NET 如何处理多种内联可能性(即,如果在 3 个不同的地方调用 func1,.NET 是尝试所有内联组合,还是全有或全无)?
      • .NET 是 JIT 编译的,因此它可以在运行时根据不同内联可能性的信息做出决策。这只是我的直觉,但如果 .NET 没有在 3 个不同的地方以不同的方式内联 func1,我会感到惊讶,因为它应该有信息来做出明智的决定。在这种情况下,您需要一本关于 JIT 技术的书。
      • 老实说,我不太了解细节。我只读过一篇关于它的文章。
      猜你喜欢
      • 2011-08-06
      • 2011-10-29
      • 1970-01-01
      • 2012-04-17
      • 2023-04-02
      • 1970-01-01
      • 1970-01-01
      • 2013-10-18
      • 1970-01-01
      相关资源
      最近更新 更多