刺激代码内联化

7

与C++等语言不同的是,在Go中你不能显式地声明inline,而是由编译器动态检测适合进行内联的函数(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中具体做出选择的决策。例如,像PHP这样的脚本语言在执行包含一个常量addSomething($a, $b)的循环时会受到极大的影响,其中对于像这样的十亿次运算,与$a + $b(内联)相比,成本几乎是荒谬的。


2
不要太担心,特别是因为你能做的事情并不多,而且每6个月就会有一个新的编译器(它可以进行更好的内联),所以你能做的事情也会随之改变。 - Volker
3个回答

14

在没有性能问题之前,你不需要关心它是否内联。无论是否内联,它都会执行相同的操作。

如果性能确实很重要并且有显着而明显的差异,那么不要依赖当前(或过去)的内联条件,自己“内联”代码(不要将其放到单独的函数中)。

规则可以在$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),其中讨论了内联优化(长篇文章,在中间位置,搜索“inline”一词)。

还有有趣的关于内联优化改进的讨论(可能在Go 1.9中实现):cmd/compile: improve inlining cost model #17566


感谢icza的解释。我一回到家就一定会看这些文章!我知道根据C++(GNU文档也指出),内联也可能导致反效果,即导致膨胀效应。我点了+1,但今天晚些时候我会先读这些文章并根据发现的标准接受你的答案:)我知道这有时是优化方法论的缺点,但我对逻辑本身比“性能增益”本身更感兴趣。 - user6834400
请注意,编译器会对调用其他已经被内联的函数的函数进行内联,只要这些函数一起仍然满足条件。特别是当“预算”开始变得紧张时,函数及其调用的任何内联函数必须适合相同的预算,例如最新版本中的40个操作或其他预算。当谈论非叶子或“中间堆栈”函数时,人们指的是调用其他未被内联的函数的函数。 - thomasrutter

3
更好的方法是不要猜测,而是进行度量!你应该相信编译器,避免试图猜测其内部工作方式,因为这会随着版本的更改而发生变化。编译器、CPU或缓存可以玩弄太多技巧,以至于无法从源代码预测性能。
如果内联使您的代码变得更大,以至于它无法再适合缓存行,那么它将比非内联版本慢得多。缓存局部性对性能的影响可能比分支更大。

我理解你的观点,Franck。我经常使用testing库,但它并不总是字面上的“解释器”,以确定它是否实际进行了内联。如果函数在更多位置中使用,则更难确定它是否进行了内联。我已经缓存了很多数据集,因此那里几乎没有额外开销。我的问题大多是关于内联本身的。尽管如此,你的观点通常是正确的,我完全同意 :)。 - user6834400

0

你正在进行一场艰苦的战斗。 Go语言并不适合你所尝试做的事情。 Go语言不可调整,它是为中等性能而设计的。 它更注重简单性而非性能,因此人们不应该在需要更精确行为(如内联)的地方使用它。 更注重性能的语言有内联的API。 可以尝试Rust、C++、C#。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接