go1.17发布blog中有这么一段描述本次大版本更新带来的性能普遍提升:
This release brings additional improvements to the compiler, namely a new way of passing function arguments and results. This change has shown about a 5% performance improvement in Go programs and reduction in binary sizes of around 2% for amd64 platforms. Support for more platforms will come in future releases.
简单来说就是,通过一种新的传参和传返回值的方法,实现了性能提升5%。go1.17发布新特性总结传送门:go1.17发布
验证一下
下面我们通过benchmark来验证一下,go1.16版本和go1.17版本的性能变化,测试代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package main
import (
"testing"
)
func FibRecursion(n int) int {
switch {
case n < 2:
return n
default:
return FibRecursion(n-1) + FibRecursion(n-2)
}
}
func BenchmarkFibRecursion(b *testing.B) {
for i := 0; i < b.N; i++ {
FibRecursion(20)
}
}
|
被测试的方法FibRecursion是一个斐波那契数列生成方法,之所以选择它,是因为根据官方的文档,性能的提升来自传参及传返回值的优化,而FibRecursion是递归运行的,方法调用次数多,这样才容易进行对比。
下面是在go1.16和go1.17两个版本下运行的benchmark测试结果:
go1.16版本的测试结果:
1
2
3
4
5
6
7
8
9
10
11
12
|
PS E:\liuwei\go1617> gov16
已切换:go1.16.3
PS E:\liuwei\go1617> go version
go version go1.16.3 windows/amd64
PS E:\liuwei\go1617> go test --bench=. --benchmem=true
goos: windows
goarch: amd64
pkg: go1617
cpu: Intel(R) Core(TM) i5-6500 CPU @ 3.20GHz
BenchmarkFibRecursion-4 25707 48020 ns/op 0 B/op 0 allocs/op
PASS
ok go1617 1.825s
|
go1.17版本的测试结果:
1
2
3
4
5
6
7
8
9
10
11
12
|
PS E:\liuwei\go1617> gov17
已切换:go1.17
PS E:\liuwei\go1617> go version
go version go1.17 windows/amd64
PS E:\liuwei\go1617> go test --bench=. --benchmem=true
goos: windows
goarch: amd64
pkg: go1617
cpu: Intel(R) Core(TM) i5-6500 CPU @ 3.20GHz
BenchmarkFibRecursion-4 33096 35767 ns/op 0 B/op 0 allocs/op
PASS
ok go1617 1.625s
|
go1.16的48020 ns/op和go1.17的35767 ns/op,对比下来,远不止5%的提升。
哪里带来的优化
不懂就看文档,关于本次性能优化的官方文档地址:https://golang.org/doc/go1.17#compiler。
文档开头一句如下:
Go 1.17 implements a new way of passing function arguments and results using registers instead of the stack.
简单翻译,就是go1.17使用寄存器代替栈来传递参数和返回值。懂的都懂,寄存器比内存快,即使内存能够缓存到cpu的高速缓存中,也还是寄存器更快。
实践出真知,我们再通过反汇编看一下,传参和返回值到底有什么变化:
示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
|
package main
func main() {
foo(3)
}
//go:noinline
func foo(n int) int {
n = 5
return n
}
|
输出go1.16的汇编代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
PS E:\liuwei\go1617> go version
go version go1.16.3 windows/amd64
PS E:\liuwei\go1617> go build -o main.exe main.go
PS E:\liuwei\go1617> go tool objdump -s main main.exe
TEXT main.main(SB) E:/liuwei/go1617/main.go
main.go:3 0x4626c0 65488b0c2528000000 MOVQ GS:0x28, CX
main.go:3 0x4626c9 488b8900000000 MOVQ 0(CX), CX
main.go:3 0x4626d0 483b6110 CMPQ 0x10(CX), SP
main.go:3 0x4626d4 762a JBE 0x462700
main.go:3 0x4626d6 4883ec18 SUBQ $0x18, SP
main.go:3 0x4626da 48896c2410 MOVQ BP, 0x10(SP)
main.go:3 0x4626df 488d6c2410 LEAQ 0x10(SP), BP
main.go:4 0x4626e4 48c7042403000000 MOVQ $0x3, 0(SP) //将传入foo方法的参数写入栈内存
main.go:4 0x4626ec e82f000000 CALL main.foo(SB) //进行foo方法调用
main.go:5 0x4626f1 488b6c2410 MOVQ 0x10(SP), BP
main.go:5 0x4626f6 4883c418 ADDQ $0x18, SP
main.go:5 0x4626fa c3 RET
main.go:3 0x4626fb 0f1f440000 NOPL 0(AX)(AX*1)
main.go:3 0x462700 e89b87ffff CALL runtime.morestack_noctxt(SB)
main.go:3 0x462705 ebb9 JMP main.main(SB)
TEXT main.foo(SB) E:/liuwei/go1617/main.go
main.go:10 0x462720 48c744241005000000 MOVQ $0x5, 0x10(SP) //将返回值写入栈内存
main.go:10 0x462729 c3 RET
|
输出go1.17的汇编代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
PS E:\liuwei\go1617> gov17
已切换:go1.17
PS E:\liuwei\go1617> go version
go version go1.17 windows/amd64
PS E:\iuwei\go1617> go build -o main.exe main.go
PS E:\liuwei\go1617> go tool objdump -s main main.exe
TEXT main.main(SB) E:/liuwei/go1617/main.go
main.go:3 0x45aae0 493b6610 CMPQ 0x10(R14), SP
main.go:3 0x45aae4 7622 JBE 0x45ab08
main.go:3 0x45aae6 4883ec10 SUBQ $0x10, SP
main.go:3 0x45aaea 48896c2408 MOVQ BP, 0x8(SP)
main.go:3 0x45aaef 488d6c2408 LEAQ 0x8(SP), BP
main.go:4 0x45aaf4 b803000000 MOVL $0x3, AX //将传入foo方法的参数写入寄存器
main.go:4 0x45aaf9 e822000000 CALL main.foo(SB) //进行foo方法调用
main.go:5 0x45aafe 488b6c2408 MOVQ 0x8(SP), BP
main.go:5 0x45ab03 4883c410 ADDQ $0x10, SP
main.go:5 0x45ab07 c3 RET
main.go:3 0x45ab08 e8f386ffff CALL runtime.morestack_noctxt.abi0(SB)
main.go:3 0x45ab0d ebd1 JMP main.main(SB)
TEXT main.foo(SB) E:/liuwei/go1617/main.go
main.go:10 0x45ab20 b805000000 MOVL $0x5, AX //将返回值写入寄存器
main.go:10 0x45ab25 c3 RET
|
go1.16版本将参数和返回值存在栈内存里进行传递,而go1.17将参数和返回值通过寄存器传递。
一方面cpu访问寄存器速度远远高于访问栈内存,另一方面,使用寄存器传递参数也减少了出入栈操作。