go
首发于go
躲避 Go 1.13 defer 性能提升的姿势

躲避 Go 1.13 defer 性能提升的姿势

本文给出使用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语句有隐式循环;


显式循环考虑以下代码

package main

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

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

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

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

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



隐式循环考虑以下代码

package main

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

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

"".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:mp.weixin.qq.com/s/BzUl

资料2:zhuanlan.zhihu.com/p/68

资料3:zhuanlan.zhihu.com/p/69


发布于 2019-09-09

文章被以下专栏收录