go1.13之Error Warp


本文给出使用go1.13 defer时应该避免的姿势。

性能提升30%的原理和defer本身的原理不做介绍。这两项可分别参考资料1,2,3。

资料1简单给出性能提升的一些关键点。重点是原来defer语法依赖的_defer数据结构只能分配在堆上,新版本中可以针对性分配在栈上。免去了systemstack()或mallocgc()带来的开销。

资料2-3详细介绍了defer原理;此原理在go1.13版本中不过时,因为go1.13的优化场景具有针对性。

简单来说,要想享受defer带来的30%性能提升。以下两种场景的代码要避免:

  1. defer语句外层嵌套有显式循环;
  2. defer语句有隐式循环;

显式循环考虑以下代码

1
2
3
4
5
6
7
package main

func main() {
	for i := 0; i <= 0; i++ {
		defer func() {}()
	}
}

看汇编结果,底层调用的是runtime.deferproc。还是老方式,即_defer对象分配在堆上。

1
2
3
4
"".main STEXT size=121 args=0x0 locals=0x20
				# ...
        0x0047 00071 (main.go:5)        CALL    runtime.deferproc(SB)
				# ...

注意循环只执行一次,实际只要它有循环次数,都是在堆上分配对象。等于性能优化享受不了。

1
2
for i := 0; i <= -1; i++ {} // 堆和栈都不分配。因为循环不被执行
for i := 0; i <= [等于或大于0的任何正整数]; i++ {} // 分配在堆上

隐式循环考虑以下代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

func main() {
	n := 1
	defer func() {}()
again:
	defer func() {}()
	if n == 1 {
		n -= 1
		goto again
	}
}

看汇编结果,只有第一个defer底层调用runtime.deferprocStack(),_defer对象分配在栈上。原因是第二个defer语句其实有一个隐式循环。

1
2
3
4
5
6
"".main STEXT size=183 args=0x0 locals=0x58
				# ...
        0x003e 00062 (main.go:5)        CALL    runtime.deferprocStack(SB)
        # ...
        0x0069 00105 (main.go:7)        CALL    runtime.deferproc(SB)
				# ...

避免以上两种情况就能享受30%的性能优化。

最后,虽然go1.13 中defer只针对部分场景性能提升了30%,但官方issue并没有close,也是希望社区有更多的case反馈,可以更通用地提升defer的性能。

未来还是美好滴。

资料1:https://mp.weixin.qq.com/s/BzUlYL9xsqgzUDOyxqgBgw

资料2:https://zhuanlan.zhihu.com/p/68702577

资料3:https://zhuanlan.zhihu.com/p/69455275