躲避 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:https://mp.weixin.qq.com/s/BzUlYL9xsqgzUDOyxqgBgw
资料2:https://zhuanlan.zhihu.com/p/68702577
资料3:https://zhuanlan.zhihu.com/p/69455275



公众号