Golang 函数内联

Golang inline

Posted by alovn on March 28, 2022

函数内联

函数内联是编译器的一种优化技术,它是指将较小的函数直接组合到调用者的函数中,这样可以减少函数调用带来的开销,不过这样编译出来的二进制体积会稍大一些。

在Go语言中,在函数前加上//go: noinline指令,就可以在编译的时候禁止对函数内联优化。

示例

下面是一个基准测试,对比下函数有没有被内联性能差距:

1
2
3
4
5
6
7
8
9
10
11
12
//go:noinline
func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}
func BenchmarkTest_max(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = max(i, 1)
    }
}

函数没有被内联的测试结果(noinline):

1
2
3
4
5
6
7
8
goos: darwin
goarch: amd64
pkg: examples/func/inline
cpu: Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz
BenchmarkTest_max
BenchmarkTest_max-8   	685833063	         1.754 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	examples/func/inline	1.669s

然后去掉//go:noinline指令后就是函数被内联的测试结果:

1
2
3
4
5
6
7
8
goos: darwin
goarch: amd64
pkg: examples/func/inline
cpu: Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz
BenchmarkTest_max
BenchmarkTest_max-8   	1000000000	         0.2923 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	examples/func/inline	3.361s

可以看到这个max函数被内联后性能提升了将近5倍多。

不过并不是所有的函数都会进行内联,Go的编译器会计算出函数内联的花费成本,所以只有操作相对简单的函数才会进行内联。如果函数内部有 for、rang、go、select等语句或函数内部有较复杂的操作时,该函数就不会被内联,比如递归函数。

除了添加noinline指令,还可以在编译时增加增加编译器选项参数-l禁止内联,不过这样会对程序中所有的函数禁止内联:

1
2
go build -gcflags="-l" main.go
go tool compile -l main.go

那么如何查看一个函数是否可以被内联呢?可以在程序编译的时候增加-m=2参数,根据打印出的编译信息,可以看出该函数是否被内联了,以及不能内联的原因是什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func fib(n int) int {
    if n < 2 {
        return n
    }
    return fib(n-1) + fib(n-2)
}

func main() {
    r := max(1, 2)
    fmt.Println(r)
    r = fib(5)
    fmt.Println(r)
}

对上面的代码进行编译:

1
2
3
4
5
6
go tool compile -m=2 main.go | grep inline

//Output
main.go:5:6: can inline max with cost 8 as: func(int, int) int { if a > b { return a }; return b }
main.go:11:6: cannot inline fib: recursive
main.go:17:6: cannot inline main: function too complex: cost 230 exceeds budget 80

根据输出结果可以看到max函数可以被内联,而fib函数由于是一个递归函数不能被内联。