В этой статье рассматривается базовая реализация интерфейса Go с точки зрения исходного кода, а также принципы реализации присваивания, отражения и утверждения интерфейса. Для сравнения используются две версии go1.8.6 и go1.9.1.
1. eface
Пустой интерфейс реализован через структуру eface, расположенную в runtime/runtime2.go:
// src/runtime/runtime2.go
// 空接口
type eface struct {
_type *_type
data unsafe.Pointer
}
Пустой интерфейс (eface) имеет два поля: информацию о типе (_type) указанного объекта и указатель данных (data). Первый взгляд_type
Поле:
// 所有类型信息结构体的公共部分
// src/rumtime/runtime2.go
type _type struct {
size uintptr // 类型的大小
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32 // 类型的Hash值
tflag tflag // 类型的Tags
align uint8 // 结构体内对齐
fieldalign uint8 // 结构体作为field时的对齐
kind uint8 // 类型编号 定义于runtime/typekind.go
alg *typeAlg // 类型元方法 存储hash和equal两个操作。map key便使用key的_type.alg.hash(k)获取hash值
gcdata *byte // GC相关信息
str nameOff // 类型名字的偏移
ptrToThis typeOff
}
_type — это общедоступное описание всех типов go, которое содержит такие детали, как сборщик мусора, отражение и т. д. Он определяет, как следует интерпретировать и обрабатывать данные, что также является его отличием от C void*.
Описание типа, требуемое для каждого типа, отличается.Например, chan должен описывать свой тип элемента в дополнение к самому chan, а map требует информацию о ключевом типе и информацию о типе значения и т. д.:
// src/runtime/type.go
// ptrType represents a pointer type.
type ptrType struct {
typ _type // 指针类型
elem *_type // 指针所指向的元素类型
}
type chantype struct {
typ _type // channel类型
elem *_type // channel元素类型
dir uintptr
}
type maptype struct {
typ _type
key *_type
elem *_type
bucket *_type // internal type representing a hash bucket
hmap *_type // internal type representing a hmap
keysize uint8 // size of key slot
indirectkey bool // store ptr to key instead of key itself
valuesize uint8 // size of value slot
indirectvalue bool // store ptr to value instead of value itself
bucketsize uint16 // size of bucket
reflexivekey bool // true if k==k for all keys
needkeyupdate bool // true if we need to update key on an overwrite
}
Первое поле информации этого типа_type
(информация о самом типе), за которой следует куча других деталей, которые нужны типу (например, информация о подтипе), чтобы при выполнении операций, связанных с типом, можно было передать слово (typ *_type
), чтобы выразить все типы, а затем передать_type.kind
Конкретный тип может быть проанализирован, и, наконец, полное «дерево_типов» типа может быть получено путем преобразования адреса.Обратитесь к функции Reflect.Type.Elem():
// reflect/type.go
// reflect.rtype结构体定义和runtime._type一致 type.kind定义也一致(为了分包而重复定义)
// Elem()获取rtype中的元素类型,只针对复合类型(Array, Chan, Map, Ptr, Slice)有效
func (t *rtype) Elem() Type {
switch t.Kind() {
case Array:
tt := (*arrayType)(unsafe.Pointer(t))
return toType(tt.elem)
case Chan:
tt := (*chanType)(unsafe.Pointer(t))
return toType(tt.elem)
case Map:
// 对Map来讲,Elem()得到的是其Value类型
// 可通过rtype.Key()得到Key类型
tt := (*mapType)(unsafe.Pointer(t))
return toType(tt.elem)
case Ptr:
tt := (*ptrType)(unsafe.Pointer(t))
return toType(tt.elem)
case Slice:
tt := (*sliceType)(unsafe.Pointer(t))
return toType(tt.elem)
}
panic("reflect: Elem of invalid type")
}
2. iface
Структура iface представляет собой непустой интерфейс:
// runtime/runtime2.go
// 非空接口
type iface struct {
tab *itab
data unsafe.Pointer
}
// 非空接口的类型信息
type itab struct {
inter *interfacetype // 接口定义的类型信息
_type *_type // 接口实际指向值的类型信息
link *itab
bad int32
inhash int32
fun [1]uintptr // 接口方法实现列表,即函数地址列表,按字典序排序
}
// runtime/type.go
// 非空接口类型,接口定义,包路径等。
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod // 接口方法声明列表,按字典序排序
}
// 接口的方法声明
type imethod struct {
name nameOff // 方法名
ityp typeOff // 描述方法参数返回值等细节
}
Непустой интерфейс (iface) сам должен сохранять методы своего интерфейса в дополнение к объектам, удовлетворяющим его интерфейс, поэтому кроме поля данных, iface описывает детали непустого интерфейса через поле табуляции , включая определение метода интерфейса, адрес реализации метода интерфейса, тип интерфейса и т. д. iface — это реализация непустого интерфейса, а не определение типа.Настоящим типом iface является interfacetype, а его первое поле по-прежнему является полем _type, описывающим его собственный тип.
Для повышения эффективности поиска реализована хеш-таблица во время выполнения (interface_type, конкретный_тип) -> itab (включая конкретный адрес реализации метода и другую информацию):
// runtime/iface.go
const (
hashSize = 1009
)
var (
ifaceLock mutex // lock for accessing hash
hash [hashSize]*itab
)
// 简单的Hash算法
func itabhash(inter *interfacetype, typ *_type) uint32 {
h := inter.typ.hash
h += 17 * typ.hash
return h % hashSize
}
// 根据interface_type和concrete_type获取或生成itab信息
func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
...
// 算出hash key
h := itabhash(inter, typ)
var m *itab
...
// 遍历hash slot链表
for m = (*itab)(atomic.Loadp(unsafe.Pointer(&hash[h]))); m != nil; m = m.link {
// 如果在hash表中找到则返回
if m.inter == inter && m._type == typ {
if m.bad {
if !canfail {
additab(m, locked != 0, false)
}
m = nil
}
...
return m
}
}
}
// 如果没有找到,则尝试生成itab(会检查是否满足接口)
m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
m.inter = inter
m._type = typ
additab(m, true, canfail)
if m.bad {
return nil
}
return m
}
// 检查concrete_type是否符合interface_type 并且创建对应的itab结构体 将其放到hash表中
func additab(m *itab, locked, canfail bool) {
inter := m.inter
typ := m._type
x := typ.uncommon()
ni := len(inter.mhdr)
nt := int(x.mcount)
xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
j := 0
for k := 0; k < ni; k++ {
i := &inter.mhdr[k]
itype := inter.typ.typeOff(i.ityp)
name := inter.typ.nameOff(i.name)
iname := name.name()
ipkg := name.pkgPath()
if ipkg == "" {
ipkg = inter.pkgpath.name()
}
for ; j < nt; j++ {
t := &xmhdr[j]
tname := typ.nameOff(t.name)
// 检查方法名字是否一致
if typ.typeOff(t.mtyp) == itype && tname.name() == iname {
pkgPath := tname.pkgPath()
if pkgPath == "" {
pkgPath = typ.nameOff(x.pkgpath).name()
}
// 是否导出或在同一个包
if tname.isExported() || pkgPath == ipkg {
if m != nil {
// 获取函数地址,并加入到itab.fun数组中
ifn := typ.textOff(t.ifn)
*(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn
}
goto nextimethod
}
}
}
// didn't find method
if !canfail {
if locked {
unlock(&ifaceLock)
}
panic(&TypeAssertionError{"", typ.string(), inter.typ.string(), iname})
}
m.bad = true
break
nextimethod:
}
if !locked {
throw("invalid itab locking")
}
// 加到Hash Slot链表中
h := itabhash(inter, typ)
m.link = hash[h]
m.inhash = true
atomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m))
}
Видно, что нет необходимости проверять, соответствует ли объект требованиям к интерфейсу каждый раз, когда интерфейс назначается, а информация itab генерируется только в первый раз, а затем информация itab может быть найдена через хеш-таблицу.
3. Назначение интерфейса
type MyInterface interface {
Print()
}
type MyStruct struct{}
func (ms MyStruct) Print() {}
func main() {
a := 1
b := "str"
c := MyStruct{}
var i1 interface{} = a
var i2 interface{} = b
var i3 MyInterface = c
var i4 interface{} = i3
var i5 = i4.(MyInterface)
fmt.Println(i1, i2, i3, i4, i5)
}
Скомпилируйте и дизассемблируйте с помощью go1.8:
$GO1.8PATH/bin/go build -gcflags '-N -l' -o tmp tmp.go
$GO1.8PATH/bin/go tool objdump -s "main\.main" tmp
...
tmp.go:18 0x1087165 e84645f8ff CALL runtime.convT2E(SB) // var i1 interface{} = a
...
tmp.go:19 0x10871bc e8ef44f8ff CALL runtime.convT2E(SB) // var i2 interface{} = b
...
tmp.go:20 0x10871f0 e86b45f8ff CALL runtime.convT2I(SB) // var i3 MyInterface = c
tmp.go:20 0x10871f5 488b442410 MOVQ 0x10(SP), AX // 返回的iface.itab地址
tmp.go:20 0x10871fa 488b4c2418 MOVQ 0x18(SP), CX // 返回的iface.data地址
tmp.go:20 0x10871ff 4889842480000000 MOVQ AX, 0x80(SP) // i3.tab = iface.itab
tmp.go:20 0x1087207 48898c2488000000 MOVQ CX, 0x88(SP) // i3.data = iface.data
tmp.go:21 0x108720f 488b842488000000 MOVQ 0x88(SP), AX
tmp.go:21 0x1087217 488b8c2480000000 MOVQ 0x80(SP), CX
tmp.go:21 0x108721f 48898c24e0000000 MOVQ CX, 0xe0(SP) // 0xe0(SP) = i3.tab
tmp.go:21 0x1087227 48898424e8000000 MOVQ AX, 0xe8(SP) // 0xe8(SP) = i3.data
tmp.go:21 0x108722f 48894c2448 MOVQ CX, 0x48(SP)
...
// var i4 interface{} = i3
tmp.go:21 0x108724b 488b8424e8000000 MOVQ 0xe8(SP), AX // 加载i3的data
tmp.go:21 0x1087253 488b4c2448 MOVQ 0x48(SP), CX // 加载i3的tab(即interfacetype地址)
tmp.go:21 0x1087258 48894c2470 MOVQ CX, 0x70(SP) // i4._type = i3.interfacetype
tmp.go:21 0x108725d 4889442478 MOVQ AX, 0x78(SP) // i4.data = i3.data
...
// var i5 = i4.(MyInterface)
tmp.go:22 0x1087299 e87245f8ff CALL runtime.assertE2I(SB)
...
Видно, что компилятор присваивает интерфейсу известный компилятору тип через convT2E и convT2I (где E представляет eface, I представляет iface, а T представляет известный компилятору тип, то есть статический тип), компилятор знает макет itab и будет проверять совместимость интерфейса во время компиляции и генерировать информацию itab, поэтому вызов convT2X, сгенерированный компилятором, обязательно будет успешным.
Для назначения между интерфейсами относительно просто назначить iface для eface, напрямую извлечь тип интерфейса и данные eface и назначить их для iface. И наоборот, вам нужно использовать утверждение интерфейса.Утверждение интерфейса выполняется с помощью таких функций, как assertE2I, assertI2I и т. д. Этот тип функции утверждения имеет две версии в зависимости от метода вызова пользователя:
i5 := i4.(MyInterface) // call conv.assertE2I
i5, ok := i4.(MyInterface) // call conv.AssertE2I2
Давайте рассмотрим несколько часто используемых реализаций функций conv и assert:
// go1.8/src/runtime/iface.go
func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
if raceenabled {
raceReadObjectPC(t, elem, getcallerpc(unsafe.Pointer(&t)), funcPC(convT2E))
}
if msanenabled {
msanread(elem, t.size)
}
if isDirectIface(t) {
// This case is implemented directly by the compiler.
throw("direct convT2E")
}
x := newobject(t)
// TODO: We allocate a zeroed object only to overwrite it with
// actual data. Figure out how to avoid zeroing. Also below in convT2I.
typedmemmove(t, x, elem)
e._type = t
e.data = x
return
}
func convT2I(tab *itab, elem unsafe.Pointer) (i iface) {
t := tab._type
if raceenabled {
raceReadObjectPC(t, elem, getcallerpc(unsafe.Pointer(&tab)), funcPC(convT2I))
}
if msanenabled {
msanread(elem, t.size)
}
if isDirectIface(t) {
// This case is implemented directly by the compiler.
throw("direct convT2I")
}
x := newobject(t)
typedmemmove(t, x, elem)
i.tab = tab
i.data = x
return
}
func assertE2I(inter *interfacetype, e eface) (r iface) {
t := e._type
if t == nil {
// explicit conversions require non-nil interface value.
panic(&TypeAssertionError{"", "", inter.typ.string(), ""})
}
r.tab = getitab(inter, t, false)
r.data = e.data
return
}
В assertE2I мы видим функцию getitab, т.е.i5=i4.(MyInterface)
, мы будем судить, удовлетворяет ли конкретный тип (MyStruct) i4 типу интерфейса MyInterface, потому что мы выполнилиvar i3 MyInterface = c
, значит, итаб уже есть в хэше[itabhash(MyInterface, MyStruct)], поэтому нет необходимости снова проверять, удовлетворяет ли интерфейс, достаточно вынуть итаб из хеш-таблицы (адрес реализации каждого метода для интерфейса был инициализирован).
А в go1.9 есть некоторые оптимизации:
1. Специализируйте convT2x для простых типов (таких как int32, string, slice) (избегайте typedmemmove):
convT2E16, convT2I16
convT2E32, convT2I32
convT2E64, convT2I64
convT2Estring, convT2Istring
convT2Eslice, convT2Islice
convT2Enoptr, convT2Inoptr
По статистике, при компиляции make.bash 93% вызовов convT2x можно оптимизировать за счет вышеуказанной специализации. Ссылаться наздесь.
2. Оптимизированы оставшиеся вызовы convT2I
Поскольку itab генерируется компилятором (см. ассемблерный код и функцию convT2I, сгенерированную go1.8 выше), компилятор может напрямую назначать itab и elem полям tab и data в iface, избегая вызовов функций и typedmemmove. Обратитесь к этой оптимизации1и2.
Конкретный ассемблерный код больше не указан, и заинтересованные студенты могут попробовать его самостоятельно.
4. Отражение типа
Рефлексия типа — это не что иное, как извлечение полей _type и данных eface{} для синтаксического анализа Реализация для TypeOf очень проста:
// 代码位于relect/type.go
// reflect.Type接口的实现为: reflect.rtype
// reflect.rtype结构体定义和runtime._type一样,只是实现了reflect.Type接口,实现了一些诸如Elem(),Name()之类的方法:
func TypeOf(i interface{}) Type {
// emptyInterface结构体定义与eface一样,都是两个word(type和data)
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
// reflect.Type.Elem()仅对复合类型有效(Array,Ptr,Map,Chan,Slice),取出其中的子类型
func (t *rtype) Elem() Type {
switch t.Kind() {
case Array:
tt := (*arrayType)(unsafe.Pointer(t))
return toType(tt.elem)
case Chan:
tt := (*chanType)(unsafe.Pointer(t))
return toType(tt.elem)
case Map:
tt := (*mapType)(unsafe.Pointer(t))
// 对mapType来说,tt.elem实际上是value的类型,可通过t.Key()来获取key类型
return toType(tt.elem)
case Ptr:
tt := (*ptrType)(unsafe.Pointer(t))
return toType(tt.elem)
case Slice:
tt := (*sliceType)(unsafe.Pointer(t))
return toType(tt.elem)
}
panic("reflect: Elem of invalid type")
}
Reflect.ValueOf более сложен, потому что он должен определить, как данные должны интерпретироваться в соответствии с типом, поэтому на самом деле Reflect.Value также содержит информацию о типе, а поле флага используется для идентификации свойств, доступных только для чтения, независимо от того, является ли оно указатель и т.д.
type Value struct {
// 值的类型
typ *rtype
// 立即数或指向数据的指针
ptr unsafe.Pointer
// type flag uintptr
// 指明值的类型,是否只读,ptr字段是否是指针等
flag
}
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
escapes(i)
return unpackEface(i)
}
// 将数据从interface{}解包为reflec.Value
func unpackEface(i interface{}) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
// NOTE: don't read e.word until we know whether it is really a pointer or not.
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, e.word, f}
}
// 将数据由reflect.Value打包为interface{}
func packEface(v Value) interface{} {
t := v.typ
var i interface{}
e := (*emptyInterface)(unsafe.Pointer(&i))
// First, fill in the data portion of the interface.
switch {
case ifaceIndir(t):
if v.flag&flagIndir == 0 {
panic("bad indir")
}
ptr := v.ptr
if v.flag&flagAddr != 0 {
c := unsafe_New(t)
typedmemmove(t, c, ptr)
ptr = c
}
e.word = ptr
case v.flag&flagIndir != 0:
e.word = *(*unsafe.Pointer)(v.ptr)
default:
e.word = v.ptr
}
e.typ = t
return i
// reflect.Value的Elem()方法仅对引用类型(Ptr和Interface{})有效,返回其引用的值
func (v Value) Elem() Value {
k := v.kind()
switch k {
case Interface:
var eface interface{}
if v.typ.NumMethod() == 0 {
eface = *(*interface{})(v.ptr)
} else {
eface = (interface{})(*(*interface {
M()
})(v.ptr))
}
x := unpackEface(eface)
if x.flag != 0 {
x.flag |= v.flag & flagRO
}
return x
case Ptr:
ptr := v.ptr
if v.flag&flagIndir != 0 {
ptr = *(*unsafe.Pointer)(ptr)
}
// The returned value's address is v's value.
if ptr == nil {
return Value{}
}
tt := (*ptrType)(unsafe.Pointer(v.typ))
typ := tt.elem
fl := v.flag&flagRO | flagIndir | flagAddr
fl |= flag(typ.Kind())
return Value{typ, ptr, fl}
}
panic(&ValueError{"reflect.Value.Elem", v.kind()})
}
Ссылаться на: