文章目录
defer1.13:先提个30%
注:本文以Go SDK v1.13进行讲解
Go1.13中defer性能的优化点,主要集中在减少defer结构体堆分配。我们通过一个例子,看看它是怎样做到的。
func A() {
defer B(10)
// code to do something
}
func B(i int) {
//......
}
像上面这样一段代码,在Go1.13中编译后的伪指令是这样的:
func A() {
var d struct {
runtime._defer
i int
}
d.siz = 0
d.fn = B
d.i = 10
r := runtime.deferprocStack(&d._defer)
if r > 0 {
goto ret
}
// code to do something
runtime.deferreturn()
return
ret:
runtime.deferreturn()
注意上面的结构体d,它由两部分组成,一个是runtime._defer结构体,一个是传给defer函数B的参数。它们被定义为函数A的局部变量,执行阶段会分配在函数栈帧的局部变量区域。接下来的runtime.deferprocStack则会把栈上分配的_defer结构体注册到defer链表。通过这样的方式避免在堆上分配_defer结构体。
值得注意的是,1.13版本中并不是所有defer都能够在栈上分配。循环中的defer,无论是显示的for循环,还是goto形成的隐式循环,都只能使用1.12版本中的处理方式在堆上分配。即使只执行一次的for循环也是一样。
//显示循环
for i:=0; i< n; i++{
defer B(i)
}
......
//隐式循环
again:
defer B()
if i<n {
n++
goto again
}
所以Go1.13中,runtime._defer结构体增加了一个字段heap,用于标识是否为堆分配。
type _defer struct {
siz int32
started bool
heap bool //标识是否为堆分配
sp uintptr
pc uintptr
fn *funcval
_panic *_panic
link *_defer
}
defer函数的执行在1.13中没有变化,依然通过deferreturn实现,依然需要把_defer结构体后面的参数与返回值空间,拷贝到defer函数的调用者栈上。只不过不是从堆上拷贝到栈上,而是从栈上的局部变量空间拷贝到参数空间。
1.13版本的defer减少了_defer结构体的堆分配,但是仍然要使用defer链表。官方提供的性能优化在30%左右,那1.14版本又做出了怎样的优化呢?