golang源码分析:go-reflect

2023-09-06 19:20:10 浏览数 (1)

使用反射的耗时是不使用的160倍左右,耗时主要分为三个部分:reflect.TypeOf(),reflect.New(),value.Field().Set(),如果我们尽量避免使用上述反射函数,或者替代上述函数是优化性能常常探索的方案。首先看下标准库里面TypeOf函数是怎么定义的:

代码语言:javascript复制
func TypeOf(i any) Type {
  eface := *(*emptyInterface)(unsafe.Pointer(&i))
  return toType(eface.typ)
}

它通过地址操作将interface类型转换为emptyInterface类型,然后取它的typ字段,其中emptyInterface定义如下:

代码语言:javascript复制
type emptyInterface struct {
  typ  *rtype
  word unsafe.Pointer
}

其中rtype实现了接口Type

代码语言:javascript复制
type Type interface {
  // Methods applicable to all types.

  // Align returns the alignment in bytes of a value of
  // this type when allocated in memory.
  Align() int

  // FieldAlign returns the alignment in bytes of a value of
  // this type when used as a field in a struct.
  FieldAlign() int

  // Method returns the i'th method in the type's method set.
  // It panics if i is not in the range [0, NumMethod()).
  //
  // For a non-interface type T or *T, the returned Method's Type and Func
  // fields describe a function whose first argument is the receiver,
  // and only exported methods are accessible.
  //
  // For an interface type, the returned Method's Type field gives the
  // method signature, without a receiver, and the Func field is nil.
  //
  // Methods are sorted in lexicographic order.
  Method(int) Method

  // MethodByName returns the method with that name in the type's
  // method set and a boolean indicating if the method was found.
  //
  // For a non-interface type T or *T, the returned Method's Type and Func
  // fields describe a function whose first argument is the receiver.
  //
  // For an interface type, the returned Method's Type field gives the
  // method signature, without a receiver, and the Func field is nil.
  MethodByName(string) (Method, bool)

  // NumMethod returns the number of methods accessible using Method.
  //
  // For a non-interface type, it returns the number of exported methods.
  //
  // For an interface type, it returns the number of exported and unexported methods.
  NumMethod() int

  // Name returns the type's name within its package for a defined type.
  // For other (non-defined) types it returns the empty string.
  Name() string

  // PkgPath returns a defined type's package path, that is, the import path
  // that uniquely identifies the package, such as "encoding/base64".
  // If the type was predeclared (string, error) or not defined (*T, struct{},
  // []int, or A where A is an alias for a non-defined type), the package path
  // will be the empty string.
  PkgPath() string

  // Size returns the number of bytes needed to store
  // a value of the given type; it is analogous to unsafe.Sizeof.
  Size() uintptr

  // String returns a string representation of the type.
  // The string representation may use shortened package names
  // (e.g., base64 instead of "encoding/base64") and is not
  // guaranteed to be unique among types. To test for type identity,
  // compare the Types directly.
  String() string

  // Kind returns the specific kind of this type.
  Kind() Kind

  // Implements reports whether the type implements the interface type u.
  Implements(u Type) bool

  // AssignableTo reports whether a value of the type is assignable to type u.
  AssignableTo(u Type) bool

  // ConvertibleTo reports whether a value of the type is convertible to type u.
  // Even if ConvertibleTo returns true, the conversion may still panic.
  // For example, a slice of type []T is convertible to *[N]T,
  // but the conversion will panic if its length is less than N.
  ConvertibleTo(u Type) bool

  // Comparable reports whether values of this type are comparable.
  // Even if Comparable returns true, the comparison may still panic.
  // For example, values of interface type are comparable,
  // but the comparison will panic if their dynamic type is not comparable.
  Comparable() bool

  // Methods applicable only to some types, depending on Kind.
  // The methods allowed for each kind are:
  //
  //  Int*, Uint*, Float*, Complex*: Bits
  //  Array: Elem, Len
  //  Chan: ChanDir, Elem
  //  Func: In, NumIn, Out, NumOut, IsVariadic.
  //  Map: Key, Elem
  //  Pointer: Elem
  //  Slice: Elem
  //  Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField

  // Bits returns the size of the type in bits.
  // It panics if the type's Kind is not one of the
  // sized or unsized Int, Uint, Float, or Complex kinds.
  Bits() int

  // ChanDir returns a channel type's direction.
  // It panics if the type's Kind is not Chan.
  ChanDir() ChanDir

  // IsVariadic reports whether a function type's final input parameter
  // is a "..." parameter. If so, t.In(t.NumIn() - 1) returns the parameter's
  // implicit actual type []T.
  //
  // For concreteness, if t represents func(x int, y ... float64), then
  //
  //  t.NumIn() == 2
  //  t.In(0) is the reflect.Type for "int"
  //  t.In(1) is the reflect.Type for "[]float64"
  //  t.IsVariadic() == true
  //
  // IsVariadic panics if the type's Kind is not Func.
  IsVariadic() bool

  // Elem returns a type's element type.
  // It panics if the type's Kind is not Array, Chan, Map, Pointer, or Slice.
  Elem() Type

  // Field returns a struct type's i'th field.
  // It panics if the type's Kind is not Struct.
  // It panics if i is not in the range [0, NumField()).
  Field(i int) StructField

  // FieldByIndex returns the nested field corresponding
  // to the index sequence. It is equivalent to calling Field
  // successively for each index i.
  // It panics if the type's Kind is not Struct.
  FieldByIndex(index []int) StructField

  // FieldByName returns the struct field with the given name
  // and a boolean indicating if the field was found.
  FieldByName(name string) (StructField, bool)

  // FieldByNameFunc returns the struct field with a name
  // that satisfies the match function and a boolean indicating if
  // the field was found.
  //
  // FieldByNameFunc considers the fields in the struct itself
  // and then the fields in any embedded structs, in breadth first order,
  // stopping at the shallowest nesting depth containing one or more
  // fields satisfying the match function. If multiple fields at that depth
  // satisfy the match function, they cancel each other
  // and FieldByNameFunc returns no match.
  // This behavior mirrors Go's handling of name lookup in
  // structs containing embedded fields.
  FieldByNameFunc(match func(string) bool) (StructField, bool)

  // In returns the type of a function type's i'th input parameter.
  // It panics if the type's Kind is not Func.
  // It panics if i is not in the range [0, NumIn()).
  In(i int) Type

  // Key returns a map type's key type.
  // It panics if the type's Kind is not Map.
  Key() Type

  // Len returns an array type's length.
  // It panics if the type's Kind is not Array.
  Len() int

  // NumField returns a struct type's field count.
  // It panics if the type's Kind is not Struct.
  NumField() int

  // NumIn returns a function type's input parameter count.
  // It panics if the type's Kind is not Func.
  NumIn() int

  // NumOut returns a function type's output parameter count.
  // It panics if the type's Kind is not Func.
  NumOut() int

  // Out returns the type of a function type's i'th output parameter.
  // It panics if the type's Kind is not Func.
  // It panics if i is not in the range [0, NumOut()).
  Out(i int) Type

  common() *rtype
  uncommon() *uncommonType
}

看完上述代码就明白了TypeOf的基本原理:利用指针操作,将输入的接口的类型信息赋值给typ,它实现了Type接口,通过这个属性就能获取类型的元信息。

上述过程优化点在哪里呢?func TypeOf(i any) Type ,的参数是一个interface,我们在调用参数的时候,将变量赋值给interface发生了逃逸,在堆上开辟内存,这个过程很低效。如果将上述过程在栈中实现,并且使用unsafe来操作内存时,进一步通过偏移量尝试操作结构体,就可以实现高效的反射功能。基于上述原理https://github.com/goccy/go-reflect诞生了。下面分析下它的源码:

bridge.go实现了,go-reflect 实现的反射类型和go源码里实现的反射的相互转换函数:都是通过指针操作来实现的。

代码语言:javascript复制
func toRT(t Type) reflect.Type {
  return type_toType(t)
}
代码语言:javascript复制
func toT(t reflect.Type) Type {
  return (Type)(((*Value)(unsafe.Pointer(&t))).ptr)
}
代码语言:javascript复制
func toRV(v Value) reflect.Value {
  return *(*reflect.Value)(unsafe.Pointer(&v))
}
代码语言:javascript复制
func toV(v reflect.Value) Value {
  return *(*Value)(unsafe.Pointer(&v))
}
代码语言:javascript复制
func toRSF(v StructField) reflect.StructField {
  return reflect.StructField{
    Name:      v.Name,
    PkgPath:   v.PkgPath,
    Type:      ToReflectType(v.Type),
    Tag:       v.Tag,
    Offset:    v.Offset,
    Index:     v.Index,
    Anonymous: v.Anonymous,
  }
}
代码语言:javascript复制
func toSF(v reflect.StructField) StructField {
  return StructField{
    Name:      v.Name,
    PkgPath:   v.PkgPath,
    Type:      ToType(v.Type),
    Tag:       v.Tag,
    Offset:    v.Offset,
    Index:     v.Index,
    Anonymous: v.Anonymous,
  }
}
代码语言:javascript复制
func toM(v reflect.Method) Method {
  return Method{
    Name:    v.Name,
    PkgPath: v.PkgPath,
    Type:    ToType(v.Type),
    Func:    toV(v.Func),
    Index:   v.Index,
  }
}
代码语言:javascript复制
func toRSC(v SelectCase) reflect.SelectCase {
  return reflect.SelectCase{
    Dir:  v.Dir,
    Chan: toRV(v.Chan),
    Send: toRV(v.Send),
  }
}

然后是init.go文件,它里面检查了,这个包实现的反射和官方反射的兼容性。如果两个包的同名函数反射结果不一致,说明是不兼容的,这是一个不错的思路用来检查自己实现的接口和官方接口是否一致。

代码语言:javascript复制
func validateTypeOf() error {
  if TypeOf(true).Kind() != Bool {
    return fmt.Errorf(errTypeOf, "bool")
  }
  if TypeOf(int(1)).Kind() != Int {
    return fmt.Errorf(errTypeOf, "int")
  }
代码语言:javascript复制
func validateValueOf() error {
  if v := ValueOf(true); v.Type().Kind() != Bool || v.Bool() != true {
    return fmt.Errorf(errValueOf, "bool")
  }
  if v := ValueOf(int(1)); v.Type().Kind() != Int || v.Int() != 1 {
    return fmt.Errorf(errValueOf, "int")
  }
代码语言:javascript复制
func validate() error {
  if err := validateTypeOf(); err != nil {
    return err
  }
  if err := validateValueOf(); err != nil {
    return err
  }
  return nil
}
代码语言:javascript复制
func init() {
  if err := validate(); err != nil {
  reflect.go
    type Value struct {
  typ Type
  ptr unsafe.Pointer
  flag
}

接着看下reflect.go文件,它定义了自己的类型rtype,实现了官方reflect.Type的接口:

代码语言:javascript复制
type Type = *rtype
 type rtype struct{}
代码语言:javascript复制
func (t *rtype) Align() int {
  return type_Align(t)
}
代码语言:javascript复制
func (t *rtype) FieldAlign() int {
  return type_FieldAlign(t)
}

并且定义了一系列标志位和Kind,和官方包几乎没有差异,其中Kind类型使用了type alias:

代码语言:javascript复制
  type flag uintptr
代码语言:javascript复制
const (
  flagKindWidth        = 5 // there are 27 kinds
  flagKindMask    flag = 1<<flagKindWidth - 1
  flagStickyRO    flag = 1 << 5
  flagEmbedRO     flag = 1 << 6
  flagIndir       flag = 1 << 7
  flagAddr        flag = 1 << 8
  flagMethod      flag = 1 << 9
  flagMethodShift      = 10
  flagRO          flag = flagStickyRO | flagEmbedRO
)
代码语言:javascript复制
type Kind = reflect.Kind
代码语言:javascript复制
const (
  Invalid Kind = iota
  Bool
  Int
  Int8
  Int16
  Int32
  Int64
  Uint
  Uint8
  Uint16
  Uint32
  Uint64
  Uintptr
  Float32
  Float64
  Complex64
  Complex128
  Array
  Chan
  Func
  Interface
  Map
  Ptr
  Slice
  String
  Struct
  UnsafePointer
)
代码语言:javascript复制
const (
  _             SelectDir = iota
  SelectSend              // case Chan <- Send
  SelectRecv              // case <-Chan:
  SelectDefault           // default
)
代码语言:javascript复制
type StructTag = reflect.StructTag
type ChanDir = reflect.ChanDir
const (
  RecvDir ChanDir             = 1 << iota // <-chan
  SendDir                                 // chan<-
  BothDir = RecvDir | SendDir             // chan
)

其它的常用类型也基本上是type alias

代码语言:javascript复制
type MapIter = reflect.MapIter
type SliceHeader = reflect.SliceHeader
type StringHeader = reflect.StringHeader
代码语言:javascript复制
type SelectCase struct {
  Dir  SelectDir // direction of case
  Chan Value     // channel to use (for send or receive)
  Send Value     // value to send (for send)
}
代码语言:javascript复制
type StructField struct {
代码语言:javascript复制
type Method struct {

reflect113.go 里面就一个函数

代码语言:javascript复制
func (v Value) IsZero() bool {
  return toRV(v).IsZero()
}

type.go,里面大量使用了linkname来引用官方包的未导出函数,并且加上了go:noescape,标记,使得上述过程不再逃逸到堆上,从而实现了高性能反射。

代码语言:javascript复制
//go:linkname type_toType reflect.toType
//go:noescape
func type_toType(t Type) reflect.Type

其它函数也类似:

代码语言:javascript复制
//go:linkname ifaceIndir reflect.ifaceIndir
//go:noescape
func ifaceIndir(Type) bool
代码语言:javascript复制
var dummy struct {
  b bool
  x interface{}
}

重写了escape函数,尽量复用dummy,结构体的内存。

代码语言:javascript复制
func escape(x interface{}) {
  if dummy.b {
    dummy.x = x
  }
}

value.go里面定义了Value的相关操作,官方的Value是个结构体

代码语言:javascript复制
type Value struct {
  // typ holds the type of the value represented by a Value.
  typ *rtype

  // Pointer-valued data or, if flagIndir is set, pointer to data.
  // Valid when either flagIndir is set or typ.pointers() is true.
  ptr unsafe.Pointer

  // flag holds metadata about the value.
  // The lowest bits are flag bits:
  //  - flagStickyRO: obtained via unexported not embedded field, so read-only
  //  - flagEmbedRO: obtained via unexported embedded field, so read-only
  //  - flagIndir: val holds a pointer to the data
  //  - flagAddr: v.CanAddr is true (implies flagIndir)
  //  - flagMethod: v is a method value.
  // The next five bits give the Kind of the value.
  // This repeats typ.Kind() except for method values.
  // The remaining 23  bits give a method number for method values.
  // If flag.kind() != Func, code can assume that flagMethod is unset.
  // If ifaceIndir(typ), code can assume that flagIndir is set.
  flag

  // A method value represents a curried method invocation
  // like r.Read for some receiver r. The typ val flag bits describe
  // the receiver r, but the flag's Kind bits say Func (methods are
  // functions), and the top bits of the flag give the method number
  // in r's type's method table.
}

go-reflect包的定义也差不多

代码语言:javascript复制
type Value struct {
  typ Type
  ptr unsafe.Pointer
  flag
}

其相关操作比如赋值或者取地址函数被重新定义了。

代码语言:javascript复制
func value_Copy(dst Value, src Value) int {
  return reflect.Copy(toRV(dst), toRV(src))
}
代码语言:javascript复制
func value_UnsafeAddr(v Value) uintptr {
  return toRV(v).UnsafeAddr()
  }

总结下,核心就是通过避免逃逸,来提升反射包的性能。

0 人点赞