使用反射的耗时是不使用的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()
}
总结下,核心就是通过避免逃逸,来提升反射包的性能。