前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang源码分析:go-reflect

golang源码分析:go-reflect

作者头像
golangLeetcode
发布2023-09-06 19:20:10
2470
发布2023-09-06 19:20:10
举报
文章被收录于专栏:golang算法架构leetcode技术php

使用反射的耗时是不使用的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()
  }

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-04-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档