44
55在 Go 1.14 中,增加了一种新的 defer 实现:open coded defer。当函数内 defer 不超过 8 个时,则会使用这种实现。
66
7+ 形如下面这样的代码:
8+
9+ ``` go
10+ defer f1 (a)
11+ if cond {
12+ defer f2 (b)
13+ }
14+ body...
15+ ```
16+
17+ 会被翻译为:
18+
19+ ``` go
20+ deferBits |= 1 <<0 // 因为函数主 block 有 defer 语句,所以这里要设置第 0 位为 1
21+ tmpF1 = f1 // 将函数地址搬运到预留的栈上空间
22+ tmpA = a // 将函数参数复制到预留的栈上空间
23+ if cond {
24+ deferBits |= 1 <<1 // 因为进入了 if block,所以这里要设置第 1 位为 1
25+ tmpF2 = f2 // 将函数地址搬运到预留的栈上空间
26+ tmpB = b // 将函数参数复制到预留的栈上空间
27+ }
28+ body...
29+ exit: // 退出函数 body
30+ if deferBits & 1 <<1 != 0 { // 检查第 1 个 bit,如果是 1,执行保存好的函数
31+ deferBits &^= 1 <<1
32+ tmpF2 (tmpB)
33+ }
34+ if deferBits & 1 <<0 != 0 { // 检查第 0 个 bit,如果是 1,执行保存好的函数
35+ deferBits &^= 1 <<0
36+ tmpF1 (tmpA)
37+ }
38+ ```
39+
40+ 这样看我们不一定知道他具体是怎么实现的,可以写个 block 更多的例子来看看实际生成的代码是什么样的:
41+
742``` go
843 1 package main
944 2
3873 31 }
3974```
4075
41- 可以分析上面的代码生成的汇编来理解这个新版的 open coded defer 到底是怎么实现的:
76+ 下面是 go tool compile -S 生成的汇编代码,简单划分一下 block 来理解一下:
4277
4378```
4479"".main STEXT size=1125 args=0x0 locals=0x160
@@ -278,6 +313,8 @@ DEFER 逻辑执行部分
278313 0x0460 01120 (open-coded-defer.go:7) JMP 0
279314```
280315
316+ 整体流程还好,不过比起以前堆上分配一个 ` _defer ` 链表,函数执行完之后直接遍历链表还是复杂太多了。
317+
281318超过 8 个 defer 时会退化回以前的 defer 链,也可以观察一下:
282319
283320``` go
@@ -299,12 +336,123 @@ func main() {
299336
300337编译出的汇编就不贴了,和以前的没什么区别。
301338
302- 可以做个简单的总结了 :
339+ 结合代码分析过程和 proposal 中的描述,总结 :
303340
304341* open coded defer 在函数内部总 defer 数量少于 8 时才会使用,大于 8 时会退化回老的 defer 链,这个权衡是考虑到程序文件的体积(个人觉得应该也有栈膨胀的考虑在)
305342* 使用 open coded defer 时,在进入函数内的每个 block 时,都会设置这个 block 相应的 bit(ORL 指令),在执行到相应的 defer 语句时,把 defer 语句的参数和调用函数地址推到栈的相应位置
306343* 在栈上需要为这些 defer 调用的参数、函数地址,以及这个用来记录 defer bits 的 byte 预留空间,所以毫无疑问,函数的 framesize 会变大
307344
345+ ## panic 处理部分
346+
347+ proposal 有两部分,一部分是讲 open coded defer 的实现,另一部分是说 panic 的处理,现在在函数中发生 panic 时,我们没有办法获得原来的 ` _defer ` 结构体了,所以必须在编译期,给每个函数准备一项 FUNCDATA,存储每一个 defer block 的地址。这样 runtime 就可以通过 FUNCDATA 和偏移找到需要执行的 defer block 在什么地方。
348+
349+ ```
350+ 0x004a 00074 (defer.go:8) FUNCDATA $2, gclocals·951c056c1b0a4d6097d387fbe928bbbf(SB)
351+ 0x004a 00074 (defer.go:8) FUNCDATA $3, "".main.stkobj(SB)
352+ 0x004a 00074 (defer.go:8) FUNCDATA $5, "".main.opendefer(SB)
353+
354+ ....
355+
356+ "".main.opendefer SRODATA dupok size=29
357+ 0x0000 30 c1 01 04 30 80 01 01 60 18 00 30 78 01 48 18 0...0...`..0x.H.
358+ 0x0010 00 30 70 01 30 18 00 30 68 01 18 18 00 .0p.0..0h....
359+ ```
360+
361+ 这里的 main.opendefer 在编译为二进制之后,可能会被优化掉。不过我们理解基本的执行流程就可以了。
362+
363+ 跟踪 runtime.gopanic 的流程,可以看到现在的 defer 上有 openDefer 的 bool 值:
364+
365+ ```
366+ (dlv) p d
367+ *runtime._defer {
368+ siz: 48,
369+ started: true,
370+ heap: false,
371+ openDefer: false,
372+ sp: 824634391712,
373+ pc: 17590156,
374+ fn: *runtime.funcval {fn: 17565744},
375+ _panic: *runtime._panic nil,
376+ link: *runtime._defer {
377+ siz: 8,
378+ started: false,
379+ heap: true,
380+ openDefer: true,
381+ sp: 824634392456,
382+ pc: 17001293,
383+ fn: *runtime.funcval nil,
384+ _panic: *runtime._panic nil,
385+ link: *runtime._defer nil,
386+ fd: unsafe.Pointer(0x10fea48),
387+ varp: 824634392528,
388+ framepc: 17000984,},
389+ fd: unsafe.Pointer(0x0),
390+ varp: 0,
391+ framepc: 0,}
392+ (dlv) n
393+ ```
394+
395+ 执行具体的 defer 逻辑在 runtime.runOpenDeferFrame,也没啥神奇的:
396+
397+ ``` go
398+ func runOpenDeferFrame (gp *g , d *_defer ) bool {
399+ done := true
400+ fd := d.fd
401+
402+ // Skip the maxargsize
403+ _, fd = readvarintUnsafe (fd)
404+ deferBitsOffset , fd := readvarintUnsafe (fd)
405+ nDefers , fd := readvarintUnsafe (fd)
406+ deferBits := *(*uint8 )(unsafe.Pointer (d.varp - uintptr (deferBitsOffset)))
407+
408+ for i := int (nDefers) - 1 ; i >= 0 ; i-- {
409+ // read the funcdata info for this defer
410+ var argWidth , closureOffset , nArgs uint32
411+ argWidth, fd = readvarintUnsafe (fd)
412+ closureOffset, fd = readvarintUnsafe (fd)
413+ nArgs, fd = readvarintUnsafe (fd)
414+ if deferBits&(1 <<i) == 0 {
415+ for j := uint32 (0 ); j < nArgs; j++ {
416+ _, fd = readvarintUnsafe (fd)
417+ _, fd = readvarintUnsafe (fd)
418+ _, fd = readvarintUnsafe (fd)
419+ }
420+ continue
421+ }
422+ closure := *(**funcval)(unsafe.Pointer (d.varp - uintptr (closureOffset)))
423+ d.fn = closure
424+ deferArgs := deferArgs (d)
425+ // If there is an interface receiver or method receiver, it is
426+ // described/included as the first arg.
427+ for j := uint32 (0 ); j < nArgs; j++ {
428+ var argOffset , argLen , argCallOffset uint32
429+ argOffset, fd = readvarintUnsafe (fd)
430+ argLen, fd = readvarintUnsafe (fd)
431+ argCallOffset, fd = readvarintUnsafe (fd)
432+ memmove (unsafe.Pointer (uintptr (deferArgs)+uintptr (argCallOffset)),
433+ unsafe.Pointer (d.varp -uintptr (argOffset)),
434+ uintptr (argLen))
435+ }
436+ deferBits = deferBits &^ (1 << i)
437+ *(*uint8 )(unsafe.Pointer (d.varp - uintptr (deferBitsOffset))) = deferBits
438+ p := d._panic
439+ reflectcallSave (p, unsafe.Pointer (closure), deferArgs, argWidth)
440+ if p != nil && p.aborted {
441+ break
442+ }
443+ d.fn = nil
444+ // These args are just a copy, so can be cleared immediately
445+ memclrNoHeapPointers (deferArgs, uintptr (argWidth))
446+ if d._panic != nil && d._panic .recovered {
447+ done = deferBits == 0
448+ break
449+ }
450+ }
451+
452+ return done
453+
454+ ` ` `
455+
308456参考资料:
309457
3104581. [open coded defer](https://github.com/golang/proposal/blob/master/design/34481-opencoded-defers.md)
0 commit comments