【问题标题】:Stimulate code-inlining刺激代码内联
【发布时间】:2017-04-28 10:24:11
【问题描述】:

与 C++ 等语言不同,您可以在其中显式声明 inline,在 Go 中,编译器会动态检测可用于内联的函数(C++ 也可以这样做,但 Go 不能两者都做)。还有一个调试选项可以查看可能发生的内联,但是网上很少有关于 go 编译器执行此操作的确切逻辑的文档。

假设我需要每隔 n 周期对一组数据重新运行一些大循环;

func Encrypt(password []byte) ([]byte, error) {
    return bcrypt.GenerateFromPassword(password, 13)
}

for id, data := range someDataSet {
    newPassword, _ := Encrypt([]byte("generatedSomething"))
    data["password"] = newPassword
    someSaveCall(id, data)
}

例如,为了使 Encrypt 被正确内联,我需要为编译器考虑什么逻辑?

我从 C++ 中知道,在没有显式 inline 关键字的情况下,通过引用传递会增加自动内联的可能性,但要理解编译器究竟做了什么来确定在 Go 中选择内联或不内联的决定并不容易。例如,如果您使用常量 addSomething($a, $b) 进行循环,那么像 PHP 这样的脚本语言会遭受巨大的损失,其中基准测试这么十亿个循环的成本与 $a + $b(内联)几乎是荒谬的。

【问题讨论】:

  • 别太担心。尤其是因为您无能为力,而且您可以使用新编译器每 6 个月更改一次(它可以做更多更好的内联)。

标签: go inline


【解决方案1】:

除非您遇到性能问题,否则您不必在意。不管是否内联,它都会做同样的事情。

如果性能确实很重要并且它产生了显着和显着的差异,那么不要依赖当前(或过去)的内联条件,自己“内联”它(不要将它放在单独的函数中)。

规则可以在$GOROOT/src/cmd/compile/internal/inline/inl.go 文件中找到。您可以使用'l' 调试标志来控制它的攻击性。

// The inlining facility makes 2 passes: first caninl determines which
// functions are suitable for inlining, and for those that are it
// saves a copy of the body. Then InlineCalls walks each function body to
// expand calls to inlinable functions.
//
// The Debug.l flag controls the aggressiveness. Note that main() swaps level 0 and 1,
// making 1 the default and -l disable. Additional levels (beyond -l) may be buggy and
// are not supported.
//      0: disabled
//      1: 80-nodes leaf functions, oneliners, panic, lazy typechecking (default)
//      2: (unassigned)
//      3: (unassigned)
//      4: allow non-leaf functions
//
// At some point this may get another default and become switch-offable with -N.
//
// The -d typcheckinl flag enables early typechecking of all imported bodies,
// which is useful to flush out bugs.
//
// The Debug.m flag enables diagnostic output.  a single -m is useful for verifying
// which calls get inlined or not, more is for debugging, and may go away at any point.

另请查看博客文章:Dave Cheney - Five things that make Go fast (2014-06-07),其中写了关于内联的文章(长文,大约在中间,搜索“内联”一词)。

还有关于内联改进的有趣讨论(可能是 Go 1.9?):cmd/compile: improve inlining cost model #17566

【讨论】:

  • 感谢您的解释 icza。我一回到家就一定会读那些文章!我从 C++(GNU 文档也说明了这一点)中知道,内联也可能导致引起腹胀效应的反作用。我为您的帖子 +1,但今天晚些时候将首先阅读文章并根据找到的标准接受您的答案:)。我知道这有时是一种优化方法,但我只是对逻辑本身感兴趣,而不是“性能增益”本身。
  • 请注意,编译器确实内联调用其他函数的函数,如果这些函数依次被内联,并且生成的函数仍然满足标准。特别是,“预算”开始变得紧张,因为函数和它调用的任何内联函数必须符合相同的预算,即 40 次操作或最新版本中的任何内容。在谈论非叶或“中间堆栈”函数时,人们指的是调用其他函数未内联的函数。
【解决方案2】:

更好的是,不要猜测,测量! 您应该信任编译器并避免尝试猜测其内部工作原理,因为它会从一个版本更改为下一个版本。 编译器、CPU 或缓存可以使用的技巧太多,无法从源代码中预测性能。

如果内联使您的代码变得更大,以至于它不再适合缓存行,使其比非内联版本慢得多怎么办?与分支相比,缓存位置对性能的影响要大得多。

【讨论】:

  • 我明白你的意思弗兰克。我经常使用testing 库,但并不总是通过字面的“解释器”来查看它是否确实进行了内联。通过测试该功能是否在更多位置使用来确定它也更难。我已经缓存了很多数据集,所以那里没有太多开销。我的问题主要是字面上的,以至于它本身就是内联的。你的观点通常是有效的,我完全同意:)。
猜你喜欢
  • 2022-11-09
  • 1970-01-01
  • 2023-01-01
  • 1970-01-01
  • 2011-09-02
  • 1970-01-01
  • 2018-06-22
  • 2010-09-10
  • 2017-12-08
相关资源
最近更新 更多