上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
6.1 defer
延迟调用通常被编译器转换为 deferproc 和 deferreturn 调用。
1.13: deferprocStack.
1.14: calling the deferred function directly. (inline)
deferproc
创建 defer 对象,保存延迟函数执行数据。
lo +------------+ +0 SP | siz | +------------+ +8 | fn | +------------+ +10 <--- argp | arg0 | +------------+ | ... | hi +------------+
// panic.go // Create a new deferred function fn with siz bytes of arguments. // The compiler turns a defer statement into a call to this. func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn gp := getg() // 获取 caller sp、pc,确认参数地址。 sp := getcallersp() argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn) callerpc := getcallerpc() // 创建 defer,保存相关信息。 d := newdefer(siz) d.link = gp._defer gp._defer = d d.fn = fn d.pc = callerpc d.sp = sp // 复制调用参数。 switch siz { case 0: // Do nothing. case sys.PtrSize: *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp)) default: memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz)) } // deferproc returns 0 normally. // a deferred func that stops a panic makes the deferproc return 1. // the code the compiler generates always checks the return value and // jumps to the end of the function if deferproc returns != 0. return0() // No code can go here - the C return register has // been set and must not be clobbered. }
同样按等级,提供二级缓存复用方式。
垃圾回收 gcStart 会调用 clearpools 清理全局缓存。
// runtime2.go type _defer struct { siz int32 started bool heap bool sp uintptr // sp at time of defer pc uintptr fn *funcval _panic *_panic // panic that is running defer link *_defer }
// runtime2.go type p struct { deferpool [5][]*_defer } type schedt struct { deferpool [5]*_defer }
优先从本地获取。如本地已空,则从全局转移一批到本地。
// panic.go // Allocate a Defer, usually using per-P pool. // Each defer must be released with freedefer. func newdefer(siz int32) *_defer { var d *_defer // 按参数长度获取等级。 sc := deferclass(uintptr(siz)) // 从缓存获取。 gp := getg() if sc < uintptr(len(p{}.deferpool)) { pp := gp.m.p.ptr() // 如果本地缓存已空,则从全局提取一批到本地。 if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil { systemstack(func() { for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil { d := sched.deferpool[sc] sched.deferpool[sc] = d.link d.link = nil pp.deferpool[sc] = append(pp.deferpool[sc], d) } }) } // 从本地缓存提取。 if n := len(pp.deferpool[sc]); n > 0 { d = pp.deferpool[sc][n-1] pp.deferpool[sc][n-1] = nil pp.deferpool[sc] = pp.deferpool[sc][:n-1] } } // 直接从堆分配。(没有复用缓存,或超出大小等级) if d == nil { // Allocate new defer+args. systemstack(func() { total := roundupsize(totaldefersize(uintptr(siz))) d = (*_defer)(mallocgc(total, deferType, true)) }) } // 存入 G._defer 链表。 d.siz = siz d.heap = true return d }
所有延迟调用(defer)都保存到 `G._defer`,等待 deferreturn 执行。
// runtime2.go type g struct { _defer *_defer }
所分配内存含头和参数。
// total size of memory block for defer with arg size sz func totaldefersize(siz uintptr) uintptr { if siz <= minDeferArgs { return minDeferAlloc } return deferHeaderSize + siz } const deferHeaderSize = unsafe.Sizeof(_defer{})
deferprocStack
相比堆分配,直接在栈内存储 defer 对象,无疑可提升性能。
d.heap = false
// deferprocStack queues a new deferred function with a defer record on the stack. // The defer record must have its siz and fn fields initialized. // The defer record must be immediately followed in memory by the arguments of the defer. func deferprocStack(d *_defer) { gp := getg() // 参数设置,其中 siz、fn 由外部设置。 d.started = false d.heap = false // !!!!!!! d.sp = getcallersp() d.pc = getcallerpc() // 添加到 G._defer 链表。 *(*uintptr)(unsafe.Pointer(&d._panic)) = 0 *(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer)) *(*uintptr)(unsafe.Pointer(&gp._defer)) = uintptr(unsafe.Pointer(d)) return0() }
func freedefer(d *_defer) { if !d.heap { return } }
deferreturn
从 G 链表找到当前函数(caller)所属 defer,拷贝参数到栈,执行。
这个栈是 caller G.stack 内存。
按调用约定,参数 arg0 对应 caller.SP。
稍后用该位置(SP)存储调用参数,并不关心它(arg0)的实际内容。
从 deferArgs 复制调用参数到到栈(SP),调用(jmpdefer)执行延迟函数。
前面调用 deferproc 时,存过同样参数,故无须担心内存不足。
// Run a deferred function if there is one. // The compiler inserts a call to this at the end of any // function which calls defer. func deferreturn(arg0 uintptr) { // 从 G._defer 链表提取。 gp := getg() d := gp._defer if d == nil { return } // deferproc 和 deferreturn 由同一 caller 调用。 // 对比 caller sp 值,可知道 G._defer 链上哪些属于当前 caller。 sp := getcallersp() if d.sp != sp { return } // 将参数复制到栈(caller SP)。 switch d.siz { case 0: // Do nothing. case sys.PtrSize: *(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d)) default: memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz)) } // 获取延迟函数。 fn := d.fn d.fn = nil gp._defer = d.link freedefer(d) // 执行延迟函数。 jmpdefer(fn, uintptr(unsafe.Pointer(&arg0))) }
调用 jmpdefer 前,堆栈状态。
+----------------+ deferreturn | fn | 执行 jmpdefer 所需参数。 +----------------+ | &arg0 | +----------------+ | ... | +----------------+ | caller.BP | ----+----------------+ | caller.IP | ----+----------------+ SP | arg0 | 延迟调用参数。 caller +----------------+ | |
// asm_amd64.s // func jmpdefer(fv *funcval, argp uintptr) // argp is a caller SP. // called from deferreturn. // 1. pop the caller // 2. sub 5 bytes from the callers return // 3. jmp to the argument TEXT runtime·jmpdefer(SB), NOSPLIT, $0-16 MOVQ fv+0(FP), DX // fv : fn MOVQ argp+8(FP), BX // argp: caller SP LEAQ -8(BX), SP // SP : caller.SP - 8 ---> caller.IP MOVQ -8(SP), BP // BP : caller.BP SUBQ $5, (SP) // caller.IP -= 5 MOVQ 0(DX), BX // jmp fn JMP BX
调用后,堆栈状态。
| | ----+----------------+ | deferreturn.IP | DX = fn, BX = &arg0 ----+----------------+ | fv: fn | +----------------+ deferreturn | argp: &arg0 | +----------------+ | ... | +----------------+ | caller.BP | ----+----------------+ SP ---> 延迟函数(fn)将从此处开始执行 !!! | caller.IP - 5 | ----+----------------+ | arg0 | caller +----------------+ | ... | +----------------+ BP
1. 将 fn 和 arg0 地址分别保存到 DX 和 BX。
2. 透过 BX - 8
,将 SP 指向 caller.IP 存储位置。
3. 透过 SP - 8
,将 deferreturn 保存的 caller.BP 存入 BP。
4. 通过 SP,将保存的 caller.IP - 5
。
5. 执行延迟函数(JMP,不会有 PC/IP 入栈)。
CALL deferreturn
指令长度 5 字节,调用该指令后IP += 5
。
caller.IP - 5
,重新指向CALL deferreturn
指令。如此,就可多次调用 deferreturn,遍历执行
G._defer
链中的延迟函数。
所以说,fn 覆盖 deferreturn 栈帧,类似
POP deferreturn
操作。最终效果,由 caller 直接
CALL fn(arg0)
假象。延迟函数保存的 BP,是 caller.BP。
延迟函数 RET 的 IP,是 caller.IP。
示例
不同版本编译结果。
package main //go:noinline func test(x int) { println(x) } func main() { defer test(100) }
1.13: 在栈上分配 _defer 对象,然后将其地址传递给 deferprocStack。
$ go build -gcflags -S "".main STEXT size=115 args=0x0 locals=0x48 0x000f 00015 (test.go:8) SUBQ $72, SP 0x0013 00019 (test.go:8) MOVQ BP, 64(SP) 0x0018 00024 (test.go:8) LEAQ 64(SP), BP 0x001d 00029 (test.go:9) MOVL $8, ""..autotmp_0+8(SP) SP +-------------+ 0 0x0025 00037 (test.go:9) LEAQ "".test·f(SB), AX | arg_d: &d | 0x002c 00044 (test.go:9) MOVQ AX, ""..autotmp_0+32(SP) +-------------+ 8 --+ 0x0031 00049 (test.go:9) MOVQ $100, ""..autotmp_0+56(SP) | siz: 8, ... | | 0x003a 00058 (test.go:9) LEAQ ""..autotmp_0+8(SP), AX +-------------+ 16 | 0x003f 00063 (test.go:9) MOVQ AX, (SP) | sp | | 0x0043 00067 (test.go:9) CALL runtime.deferprocStack(SB) +-------------+ 24 | 0x0048 00072 (test.go:9) TESTL AX, AX | pc | | 0x004a 00074 (test.go:9) JNE 92 +-------------+ 32 +--> d: _defer | fn: test.f | | 0x004c 00076 (test.go:10) XCHGL AX, AX +-------------+ 40 | 0x004d 00077 (test.go:10) CALL runtime.deferreturn(SB) | _panic | | 0x0052 00082 (test.go:10) MOVQ 64(SP), BP +-------------+ 48 | 0x0057 00087 (test.go:10) ADDQ $72, SP | _link | | 0x005b 00091 (test.go:10) RET +-------------+ --+ 0x005c 00092 (test.go:9) XCHGL AX, AX 1.13 runtime2.go 0x005d 00093 (test.go:9) CALL runtime.deferreturn(SB) 0x0062 00098 (test.go:9) MOVQ 64(SP), BP 0x0067 00103 (test.go:9) ADDQ $72, SP 0x006b 00107 (test.go:9) RET
1.14: 直接调用。
并不总是有效。比如放到循环内,就退回到 deferproc。
"".main STEXT size=118 args=0x0 locals=0x28 0x000f 00015 (test.go:8) SUBQ $40, SP 0x0013 00019 (test.go:8) MOVQ BP, 32(SP) 0x0018 00024 (test.go:8) LEAQ 32(SP), BP 0x001d 00029 (test.go:8) MOVQ $0, AX SP +----------+ 0 0x0024 00036 (test.go:8) MOVQ AX, 24(SP) | | 0x0029 00041 (test.go:8) MOVB $0, ""..autotmp_0+15(SP) +----------+ 8 0x002e 00046 (test.go:9) LEAQ "".test·f(SB), AX | | 0x0035 00053 (test.go:9) MOVQ AX, ""..autotmp_1+24(SP) +----------+ 16 0x003a 00058 (test.go:9) MOVQ $100, ""..autotmp_2+16(SP) | 100 | 0x0043 00067 (test.go:10) MOVB $0, ""..autotmp_0+15(SP) +----------+ 24 | test.f | 0x0048 00072 (test.go:10) MOVQ ""..autotmp_2+16(SP), AX +----------+ 32 0x004d 00077 (test.go:10) MOVQ AX, (SP) 0x0051 00081 (test.go:10) CALL "".test(SB) 0x0056 00086 (test.go:10) MOVQ 32(SP), BP 0x005b 00091 (test.go:10) ADDQ $40, SP 0x005f 00095 (test.go:10) RET 0x0060 00096 (test.go:10) CALL runtime.deferreturn(SB) 0x0065 00101 (test.go:10) MOVQ 32(SP), BP 0x006a 00106 (test.go:10) ADDQ $40, SP 0x006e 00110 (test.go:10) RET
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论