Реализация интерфейса Go

Go

В этой статье рассматривается базовая реализация интерфейса 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()})
}

Ссылаться на:

  1. Краткое руководство по сборке Golang
  2. Анализ исходного кода интерфейса Go