网络异常,图片无法展示
|
1.14版本中的defer
在go1.14
中,官方又对defer
做了升级,据说这次升级把速度提升了一个量级。
网络异常,图片无法展示
|
在编译期间,会直接把defer
放到函数末尾去执行,省去了_defer
结构体和链表的使用。官方把这种方法命名为:开放编码(Open Coded)
不过需要满足以下条件,否则并不会使用开放编码:
- 函数的
defer
数量少于或者等于 8 个; - 函数的
defer
关键字不能在循环中执行; - 函数的
return
语句与defer
语句的乘积小于或者等于 15 个;
延迟比特
为什么上面说defer
的数量要小于等于8个呢?这是由于延迟比特的限制。延迟比特只有8个,默认值为0,每个对应一个defer
,延迟比特的作用是判断defer
语句到底要不要执行,例如:
package main import "fmt" func main() { i:=1 if i==1{ defer fmt.Println("defer") } }
defer
外面有一个if
判断语句,当判断语句为true
时,就会把对应的defer
比特位设为1
。然后在函数末尾每个defer
都会判断对应的比特位记录是否为1
,如果为1
就执行,否则就不执行。
网络异常,图片无法展示
|
使用时机
在当前版本中,defer
一共有三种执行方式,那go
到底是如何判断当前的defer
是该用哪种方式呢?
代码生成阶段的 cmd/compile/internal/gc.state.stmt
会负责处理程序中的 defer
,该函数会根据条件的不同,使用三种不同的机制处理该关键字:
func (s *state) stmt(n *Node) { ... switch n.Op { case ODEFER: if s.hasOpenDefers { s.openDeferRecord(n.Left) // 开放编码 } else { d := callDefer // 堆分配 if n.Esc == EscNever { d = callDeferStack // 栈分配 } s.callResult(n.Left, d) }func (s *state) stmt(n *Node) { ... } }
panic问题
虽然最新版本中的defer
速度非常快,但是当程序发送panic
时,在这之后的正常逻辑就都不会执行了,而是直接去执行defer链表
。那些使用**开放地址(open coded)在函数内展开,因而没有被注册到链表的defer
函数要通过栈扫描的方式来发现。所以1.14版本中就添加了几个字段用来辅助panic
的栈扫描。
网络异常,图片无法展示
|