上卷 程序设计
中卷 标准库
- 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
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
7.1 实现
接口本身一样是静态类型,内部使用 itab
结构存储运行期所需的相关类型信息。
更多细节,可参考《源码剖析,其他》。
// runtime/runtime2.go type iface struct { tab *itab // 类型和方法表。 data unsafe.Pointer // 目标对象指针。 } type itab struct { inter *interfacetype // 接口类型。 _type *_type // 目标类型。 hash uint32 _ [4]byte fun [1]uintptr // 方法表。 offset: 24, 0x18 }
内部实现
输出编译结果,查看接口结构存储的具体内容。
type Mer interface { A() } // --------------------- type Ner interface { Mer B(int) C(string) string } // --------------------- type N int func (N) A() {} func (*N) B(int) {} func (*N) C(string) string { return "" } func (*N) D() {} // --------------------- func main() { var n N var t Ner = &n t.A() }
$ go build -gcflags "-N -l -S" "".main STEXT LEAQ go.itab.*"".N,"".Ner(SB), SI go.itab.*"".N,"".Ner SRODATA 0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0010 57 9a 14 c4 00 00 00 00 00 00 00 00 00 00 00 00 0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 rel 0+8 t=1 type."".Ner+0 rel 8+8 t=1 type.*"".N+0 rel 24+8 t=-32767 "".(*N).A+0 rel 32+8 t=-32767 "".(*N).B+0 rel 40+8 t=-32767 "".(*N).C+0 ; 不含接口未声明的 *N.D
编译器将接口和目标类型组合,生成实例( go.itab.*"".N,"".Ner
)。
其中有接口和目标类型引用,并在方法表(24, 0x18)静态填入具体方法地址。
前文提及,接口有个重要特征:赋值给接口时,会复制目标对象。
func main() { var n N = 100 var t1 Mer = n // copy n t1.A() var t2 Ner = &n // copy ptr t2.B(1) }
(gdb) info locals t1 = { tab = 0x477d38 <N,main.Mer>, data = 0xc000032730 // data = &n_copy } t2 = { tab = 0x477d78 <N,main.Ner>, data = 0xc000032728 // data = &n } n = 100 (gdb) p/x &n $1 = 0xc000032728 (gdb) x/1xg 0xc000032730 ; t1.data。 0xc000032730: 0x0000000000000064
因不可寻址(unaddressable),故无法修改接口内部存储的复制品。
func main() { var n N = 100 // var t1 Mer = n // p := &(t1.(N)) // ~~~~~~~~ cannot take address var t2 Ner = &n // 接口存储的是指针。 *(t2.(*N)) = 200 // 透过指针修改目标对象。 println(n) // 200 }
动态调用
以接口调用方法,需通过方法表动态完成。
//go:noinline func test(n Ner) { n.B(9) } func main() { var n N = 100 var i Ner = &n test(i) }
# 相比动态调用,内存逃逸才是接口导致的最大性能问题。 $ go build -gcflags "-m" moved to heap: n $ go tool objdump -S -s "main\.main" ./test func main() { var n N = 100 0x455294 LEAQ 0x5d45(IP), AX 0x45529b NOPL 0(AX)(AX*1) 0x4552a0 CALL runtime.newobject(SB) ; heap alloc 0x4552a5 MOVQ $0x64, 0(AX) test(i) 0x4552ac MOVQ AX, BX ; .data 0x4552af LEAQ go.itab.*main.N,main.Ner(SB), AX 0x4552b6 CALL main.test(SB) } $ go tool objdump -S -s "main\.test" ./test func test(n Ner) { n.B(9) 0x45523e MOVQ 0x20(AX), CX ; .itab + 32 -> B() 0x455242 MOVQ BX, AX ; .data 0x455245 MOVL $0x9, BX ; argument 0x45524a CALL CX ; B(.data, 0x9) }
编译器尝试以内联等方式优化,消除(或减少)因动态调用和内存逃逸导致的性能问题。
func test(n Ner) { n.B(9) } func main() { var n N = 100 var i Ner = &n test(i) }
优化后的代码,完全抹掉了接口的痕迹(内存逃逸和动态调用)。
$ go build -gcflags "-m" can inline test can inline main inlining call to test $ go tool objdump -S -s "main\.main" ./test func main() { var n N = 100 0x455234 MOVQ $0x64, 0x10(SP) n.B(9) 0x45523e LEAQ 0x10(SP), AX 0x455243 MOVL $0x9, BX 0x455248 CALL main.(*N).B(SB) }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论