//src/runtime/runtime2.go type iface struct { tab *itab //itab 存放型別及方法指標資訊 data unsafe.Pointer //資料資訊 }可以看到 iface 結構很簡單,有兩個指標型別欄位。
//src/runtime/runtime2.go type itab struct { inter *interfacetype //介面自身的靜態型別 _type *_type //_type 就是介面存放的具體範例的型別(動態型別) //hash 存放具體型別的 Hash 值 hash uint32 // copy of _type.hash. Used for type switches. _ [4]byte fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter. }itab 有 5 個欄位:
//src/runtime/type.go type type struct { size uintptr // 大小 ptrdata uintptr //size of memory prefix holding all pointers hash uint32 //型別Hash tflag tflag //型別的特徵標記 align uint8 //_type 作為整體交量存放時的對齊位元組數 fieldalign uint8 //當前結構欄位的對齊位元組數 kind uint8 //基礎型別列舉值和反射中的 Kind 一致,kind 決定了如何解析該型別 alg *typeAlg //指向一個函數指標表,該錶有兩個函數,一個是計算型別 Hash 函 //數,另一個是比較兩個型別是否相同的 equal 函數 //gcdata stores the GC type data for the garbage collector. //If the KindGCProg bit is set in kind, gcdata is a GC program. //Otherwise it is a ptrmask bitmap. See mbitmap.go for details. gcdata *byte //GC 相關資訊 str nameOff //str 用來表示型別名稱字串在編譯後二進位制檔案中某個 section //的偏移量 //由連結器負責填充 ptrToThis typeOff //ptrToThis 用來表示型別元資訊的指標在編譯後二進位制檔案中某個 //section 的偏移量 //由連結器負責填充 }_type 包含所有型別的共同元資訊,編譯器和執行時可以根據該元資訊解析具體型別、型別名存放位置、型別的 Hash 值等基本資訊。
//src/runtime/type.go
//獲取 _type 的 name
func resolveNameOff(ptrInModule unsafe.Pointer , off nameOff) name {}
//獲取 _type 的副本
func resolveTypeOff(ptrInModule unsafe.Pointer , off typeOff) *_type {}
接下來看一下介面的型別元資訊的資料結構。範例如下:注意:Go語言型別元資訊最初由編譯器負責構建,並以表的形式存放在編譯後的物件檔案中,再由連結器在連結時進行段合併、符號重定向(填充某些值)。這些型別資訊在介面的動態呼叫和反射中被執行時參照。
//描述介面的型別 type interfacetype struct { typ _type //型別通用部分 pkgpath name //介面所屬包的名字資訊, name 記憶體放的不僅有名稱,還有描述資訊 mhdr []imethod //介面的方法 } //介面方法元資訊 type imethod struct { name nameOff //方法名在編譯後的 section 裡面的偏移量 ityp typeOff //方法型別在編譯後的 section 裡面的偏移量 }
//iface.go package main type Caler interface { Add (a , b int) int Sub (a , b int) int } type Adder struct {id int } //go:noinline func (adder Adder) Add(a, b int) int { return a + b } //go:noinline func (adder Adder) Sub(a , b int) int { return a - b } func main () { var m Caler=Adder{id: 1234} m.Add(10, 32) }生成組合程式碼:
go build -gcflags= "-S - N -l" iface.go >iface.s 2>&1
接下來分析 main 函數的組合程式碼,非關鍵邏輯已經去掉:"".main STEXT size=151 args=0x0 locals=0x40 ... 0x000f 00015 (src/iface.go:16) SUBQ $64, SP 0x0013 00019 (src/iface.go:16) MOVQ BP, 56(SP) 0x0018 00024 (src/iface.go:16) LEAQ 56(SP), BP為 main 函數堆戰開闢空間並儲存原來的 BP 指標,這是函數呼叫前編譯器的固定動作。
0x00ld 00029 (src/iface.go:17) MOVQ $0, ""..autotmp_1+32(SP)
0x0026 00038 (src/iface.go:17) MOVQ $1234, ""..autotmp_1+32(SP)
0x002f 00047 (src/iface.go:17) LEAQ go.itab."".Adder,"".Caler(SB),AX
0x0036 00054 (src/iface.go:17) MOVQ AX, (SP)
0x003a 00058 (src/iface.go:17) LEAQ ""..autotmp_1+32(SP), AX
0x003f 00063 (src/iface.go:17) MOVQ AX, 8(SP)
0x0044 00068 (src/iface.go:17) PCDATA $0, $0
0x0044 00068 (src/iface.go:17) CALL runtime.convT2I64(SB)
runtime.convT2I64 函數是執行時介面動態呼叫的核心函數。runtime 中有一類這樣的函數,看一下 runtime.convT2I64 的原始碼:func convT2I64(tab *itab, elem unsafe.Pointer) (i iface) { t := tab._type if raceenabled { raceReadObjectPC(t, elem, getcallerpc(unsafe.Pointer(&tab)), funcPC(convT2I64)) } if msanenabled { msanread (elem, t.size) } var x unsafe.Pointer if *(uint64) (elem) == 0 { x = unsafe.Pointer(&zeroVal[0]) } else { x = mallocgc(8, t, false) *(*uint64) (x) = *(*uint64) (elem) } i.tab = tab i.data = x return }從上述原始碼可以清楚地看出,runtime.convT2I64 的兩個引數分別是 *itab 和 unsafe.Pointer 型別,這兩個引數正是上文傳遞進去的兩個引數值:go.itab."".Adder, "".Caler(SB) 和指向 Adder 物件複製的指標。
0x0049 00073 (src/iface.go:17) MOVQ 24(SP), AX
0x004e 00078 (src/iface.go:17) MOVQ 16(SP), CX
0x0053 00083 (src/iface.go:17 ) MOVQ CX, "".m+40(SP)
0x0058 00088 (src/iface.go:17 ) MOVQ AX, "".m+48(SP)
0x00Sd 00093 (src/iface.go:18) MOVQ "".m+40(SP), AX
0x0062 00098 (src/iface.go:18) MOVQ 32(AX), AX
0x0066 00102 (src/iface.go:18) MOVQ "".m+48(SP), ex
0x006b 00107 (src/iface.go:18) MOVQ $10, 8(SP)
0x0074 00116 (src/iface.go:18) MOVQ $32, 16(SP)
0x007d 00125 (src/iface.go:18) MOVQ CX, (SP)
0x0081 00129 (src/iface.go:18) PCDATA $0, $0
0x0081 00129 (src/iface.go:18) CALL AX
type itab struct { inter *interfacetype _type *type link *itab hash uint32 //copy of _type.hash.Used for type switches. bad bool //type does not implement interface inhash bool //has this itab been added to hash? unused [2]byte fun [1] uintptr //variable sized }32(AX) 正好是函數指標的位置, 即存放 Adder *Add() 方法指標的地址(注意:編譯器將接收者為值型別的 Add 方法轉換為指標的 Add 方法,編譯器的這種行為是為了方便呼叫和優化)。
func(reciver, param1, param2)
。 var m Caler = Adder{id: 1234}
。#編譯 #go build -gcflag s= "-N -l" iface.go #readelf -s -W iface legrep 'itab' 60:000000000047b220 0 OBJECT LOCAL DEFAULT 5 runtime.itablink 61:000000000047b230 0 OBJECT LOCAL DEFAULT 5 runtime.eitablink 88:00000000004aa100 48 OBJECT GLOBAL DEFAULT 8 go.itab.main.Adder, main.Caler 214:00000000004aa080 40 OBJECT GLOBAL DEFAULT 8 go.itab.runtime.errorString, error 418:00000000004095e0 1129 FUNC GLOBAL DEFAULT 1 runtime.getitab 419:0000000000409a50 1665 FUNC GLOBAL DEFAULT 1 runtime.additab 420:000000000040a0e0 257 FUNC GLOBAL DEFAULT 1 runtime.itabsinit可以看到符號表裡面 go.itab.main.Adder, main.Caler 對應本程式裡面 itab 的元資訊,它被存放在第 8 個段中。我們來看一下第 8 個段是什麼段?
#readelf -S -W iface |egrep '\[8] | I Nr'
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[8]. noptrdata PROGBITS 00000000004aa000 OaaOOO 000a78 00 WA 0 0 32
package main import ( "testing" ) type identifier interface { idInline() int32 idNoInline() int32 } type id32 struct{ id int32 } func (id *id32) idinline() int32 { return id.id } //go:noinline func (id *id32) idNoinline() int32 { return id.id } var escapeMePlease *id32 //主要作用是強制變數記憶體在 heap 上分配 //go:noinline func escapeToHeap(id *id32) identifier { escapeMePlease = id return escapeMePlease } //直接呼叫 func BenchmarkMethodCall_direct(b *testing.B) { // var myID int32 b.Run("single/noinline", func(b *testing.B) { m := escapeToHeap(&id32{id: 6754}).(*id32) b.ResetTimer () for i := 0; i < b.N; i++ { //CALL "".(*id32).idNoinline(SB) //MOVL 8(SP), AX //MOVQ "".&myID+40(SP), CX //MOVL AX, (CX) myID = m.idNoInline() } } b.Run ("single/inline", func(b *testing.B) { m := escapeToHeap(&id32{id: 6754}).(*id32) b.ResetTimer() for i: = 0; i < b.N; i++ { //MOVL (DX), SI //MOVL SI, (CX) myID = m.idinline() } }) } //介面呼叫 func BenchmarkMethodCall_interface(b *testing.B) { // var myID int32 b.Run("single/noinline", func(b *testing.B) { m := escapeToHeap(&id32{id: 6754}) b.ResetTimer() for i := 0; i < b.N ; i++ { // MOVQ 32(AX), CX // MOVQ "".m.data+40(SP), DX // MOVQ DX, (SP) // CALL CX // MOVL 8(SP), AX // MOVQ "".&myID+48(SP), CX // MOVL AX, (CX) myID = m.idNoInline() } }) b.Run("single/inline", func(b *testing.B) { m := escapeToHeap(&id32{id: 6754}) b.ResetTimer() for i := 0; i < b.N; i++ { //MOVQ 24(AX), CX //MOVQ "".m.data+40(SP), DX //MOVQ DX, (SP) //CALL CX //MOVL 8(SP), AX //MOVQ "". &myID+48(SP), ex //MOVL AX, (CX) myID = m.idinline() } }) } // func main() {}
//直接呼叫 #go test -bench= 'BenchmarkMethodCall_direct/single/noinline' -cpu=1 -count=5 iface_bench_test.go goos:linux goarch:amd64 BenchmarkMethodCall_direct/single/noinline 2000000000 2.00 ns/op BenchmarkMethodCall_direct/single/noinline 2000000000 1.97 ns/op BenchmarkMethodCall_direct/single/noinline 2000000000 1.97 ns/op BenchmarkMethodCall_direct/single/noinline 2000000000 1.94 ns/op BenchmarkMethodCall_direct/single/noinline 2000000000 1.97 ns/op PASS ok command-line-arguments 20.682s //介面呼叫 #go test -bench='BenchmarkMethodCall_interface/single/noinline' -cpu=1 -count=5 iface_bench_test.go goos:linux goarch:amd64 BenchmarkMethodCall_interface/single/noinline 1000000000 2.18 ns/op BenchmarkMethodCall_interface/single/noinline 1000000000 2.16 ns/op BenchmarkMethodCall_interface/single/noinline 1000000000 2.17 ns/op BenchmarkMethodCall_interface/single/noinline 1000000000 2.15 ns/op BenchmarkMethodCall_interface/single/noinline 1000000000 2.16 ns/op PASS ok command-line-arguments 11.930s
//go/src/runtime/runtime2.go //空介面 type eface struct { _type *_type data unsafe.Pointer }從 eface 的資料結構可以看出,空介面不是真的為空,其保留了具體範例的型別和值拷貝,即便存放的具體型別是空的,空介面也不是空的。