答案:反射是 Go 语言在编译时未知类型的情况下,操作变量、查看值、调用方法及操作布局的机制。基础组成包括:
答案:
答案:
答案:
答案:
答案: 字段值设置优化:缓存Field对象(避免重复查找字段); 使用unsafe.Pointer直接操作内存(减少反射调用开销)。
Go语言提供了一种机制,在编译时不知道类型的情况下,可更新变量、在运行时查看值、调用方法以及直接对它们的布局进行操作,这种机制称为反射 reflect 实现了运行时的反射能力,能够让程序操作不同类型的对象。反射包中有两对非常重要的函数和类型,两个函数分别是:
两个类型是 reflect.Type 和 reflect.Value,它们与上边两个函数是一一对应的关系
reflect中Type接口
type Type interface {
Method(int) Method // 方法返回类型的方法集中的第 i 个方法
MethodByName(string) (Method, bool) // MethodByName 返回类型的方法集中具有该名称的方法和指示是否找到该方法的布尔值
NumMethod() int // NumMethod 返回使用 Method 可访问的方法数
Name() string //类型名称
PkgPath() string //包路径
Size() uintptr //返回类型大小
String() string //类型
Kind() Kind //反射对象的类型
Implements(u Type) bool //反射对象实现某接口
AssignableTo(u Type) bool // 是否可以赋值给 u
ConvertibleTo(u Type) bool // 是否可以类型转换成 u
...
}
reflect 中 Value 结构体
type Value struct { //这些成员都是私有的,非导出的
typ *rtype // typ保存由Value表示的值的类型
ptr unsafe.Pointer // 指向数据的指针
flag //标记保存有关该值的元数据
}
func (v Value) Elem() Value //获取接口、指针的数据。同样是一个Value对象。
func (v Value) Set(x Value) //设置值
第一法则 从inerface{}变量可以反射出反射对象 reflect.TypeOf 和 reflect.ValueOf 函数是完成数据与反射数据的转换的方法,如果我们认为 Go 语言的类型和反射类型处于两个不同的世界,那么这两个函数就是连接这两个世界的桥梁
package main
import (
"fmt"
"reflect"
)
func main() {
author := "draven"
fmt.Println("TypeOf author:", reflect.TypeOf(author)) //author: string
fmt.Println("ValueOf author:", reflect.ValueOf(author))} //author: draven
}
有了反射变量的类型之后,我们可以通过 Method 方法获得类型实现的方法,通过 Field 获取类型包含的全部字段。对于不同的类型,我们也可以调用不同的方法获取相关信息
package main
import (
"fmt"
"reflect"
)
func main() {
x := 10
name := "Go Lang"
type Book struct {
name string
author string
}
sampleBook := Book {"Reflection in Go", "John"}
fmt.Println(reflect.TypeOf(x).Kind()) // int
fmt.Println(reflect.TypeOf(name).Kind()) // string
fmt.Println(reflect.TypeOf(sampleBook).Kind()) // struct
}
方法执行
type foo struct {
name string
}
func (f *foo) Test() {
fmt.Println("hi " + f.name)
}
func main() {
temp := foo{
name: "test",
}
fooV := reflect.ValueOf(&temp)
fooV.MethodByName("Test").Call(nil) // hi test
}
第二法则 可以从反射对象获取interface{}变量既然能够将接口类型的变量转换成反射对象,那么一定需要其他方法将反射对象还原成接口类型的变量,reflect 中的 reflect.Value.Interface 就能完成这项工作:
v := reflect.ValueOf(1)
v.Interface().(int)
从反射对象到接口值的过程是从接口值到反射对象的镜面过程,两个过程都需要经历两次转换:
第三法则 如果想要更新一个reflect.Value,那么它持有的值一定是可以被更新
func main() {
i := 1
v := reflect.ValueOf(i)
v.SetInt(10)
fmt.Println(i)
}
panic: reflect: reflect.flag.mustBeAssignable using unaddressable value
因为go 方法是值传递,所以我们得到的反射对象跟最开始的变量没有任何关系,那么直接修改反射对象无法改变原始变量,程序为了防止错误就会崩溃。
func main() {
i := 1
v := reflect.ValueOf(&i)
v.Elem().SetInt(10)
fmt.Println(i)
}
Go 语言的 interface{} 类型在语言内部是通过 reflect.emptyInterface 结体表示
type emptyInterface struct {
typ *rtype //变量类型
word unsafe.Pointer //变量数据
}
reflect.TypeOf 函数实现将传入的变量隐式转换成 reflect.emptyInterface 类型并获取其中存储的类型信息 reflect.rtype:
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
func toType(t *rtype) Type {
if t == nil {
return nil
}
return t
}
reflect.ValueOf 函数首先将对象逃逸到堆中,然后通过 reflect.unpackEface 获取reflect.Value 结构体 unpackEface中将类型和值指针包装成Value 返回
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
escapes(i)
return unpackEface(i)
}
func unpackEface(i interface{}) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, e.word, f}
}
package main
import (
"fmt"
"reflect"
)
type Temp struct {
temp string
}
func main() {
temp := Temp{temp: "123"}
typeP := reflect.TypeOf(temp)
fmt.Println(typeP.Name())
}
go build -gcflags="-S -N" main.go
MOVQ AX, ""..autotmp_8+112(SP) //将temp 转换成emptyInterface
MOVQ $3, ""..autotmp_8+120(SP)//将temp 转换成emptyInterface
LEAQ type."".Temp(SB), AX //Tmep 的类型
MOVQ AX, reflect.i+80(SP) //放到调用reflect.TypeOf 栈中
LEAQ ""..autotmp_8+112(SP), AX //temp 值
MOVQ AX, reflect.i+88(SP) //放到调用reflect.TypeOf 栈中
从上面这段截取的汇编语言,我们可以发现在函数调用之前已经发生了类型转换,上述指令将 结构体的变量转换成了占用 16 字节autotmp_8+112(SP) ~ autotmp_8+120(SP)的接口,分别是 emptyInterface类型和数据两个 LEAQ 指令分别获取了类型的指针 type.Temp(SB)以及变量 temp所在的地址。
当我们想要更新 reflect.Value 时,就需要调用 reflect.Value.Set 更新反射对象,该方法会调用 reflect.flag.mustBeAssignable 和 reflect.flag.mustBeExported 分别检查当前反射对象是否是可以被设置的以及字段是否是对外公开的:
func (v Value) Set(x Value) {
v.mustBeAssignable()
x.mustBeExported()var target unsafe.Pointer
if v.kind() == Interface {
target = v.ptr
}
x = x.assignTo("reflect.Set", v.typ, target)
typedmemmove(v.typ, v.ptr, x.ptr)
}
reflect.Value.Set 会调用 reflect.Value.assignTo 并返回一个新的反射对象,这个返回的反射对象指针会直接覆盖原反射变量。
func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value {
if v.flag&flagMethod != 0 {
v = makeMethodValue(context, v)
}
switch {
case directlyAssignable(dst, v.typ):
// Overwrite type so that they match.
// Same memory layout, so no harm done.
fl := v.flag&(flagAddr|flagIndir) | v.flag.ro()
fl |= flag(dst.Kind())
return Value{dst, v.ptr, fl}
case implements(dst, v.typ):
if target == nil {
target = unsafe_New(dst)
}
if v.Kind() == Interface && v.IsNil() {
// A nil ReadWriter passed to nil Reader is OK,
// but using ifaceE2I below will panic.
// Avoid the panic by returning a nil dst (e.g., Reader) explicitly.
return Value{dst, nil, flag(Interface)}
}
x := valueInterface(v, false)
if dst.NumMethod() == 0 {
*(*any)(target) = x
} else {
ifaceE2I(dst, x, target)
}
return Value{dst, target, flagIndir | flag(Interface)}
}
// Failed.
panic(context + ": value of type " + v.typ.String() + " is not assignable to type " + dst.String())
}
reflect.Value.assignTo 会根据当前和被设置的反射对象类型创建一个新的 reflect.Value 结构体:
func (v Value) Call(in []Value) []Value {
v.mustBe(Func)
v.mustBeExported()
return v.call("Call", in)
}
方法首先对函数类型和可见性进行校验。然后执行 value.call方法 v.call执行方法过程包括下面几部分
nout := t.NumOut()
frametype, _, retOffset, _, framePool := funcLayout(t, rcvrtype)
// Allocate a chunk of memory for frame if needed.
var stackArgs unsafe.Pointer
if frametype.size != 0 {
if nout == 0 {
stackArgs = framePool.Get().(unsafe.Pointer)
} else {
// Can't use pool if the function has return values.
// We will leak pointer to args in ret, so its lifetime is not scoped.
stackArgs = unsafe_New(frametype)
}
}
// Handle arguments.
for i, v := range in {
v.mustBeExported()
targ := t.In(i).(*rtype)
// TODO(mknyszek): Figure out if it's possible to get some
// scratch space for this assignment check. Previously, it
// was possible to use space in the argument frame.
v = v.assignTo("reflect.Value.Call", targ, nil)
stepsLoop:
for _, st := range abi.call.stepsForValue(i + inStart) {
switch st.kind {
case abiStepStack:
// Copy values to the "stack."
addr := add(stackArgs, st.stkOff, "precomputed stack arg offset")
if v.flag&flagIndir != 0 {
typedmemmove(targ, addr, v.ptr)
} else {
*(*unsafe.Pointer)(addr) = v.ptr
}
// There's only one step for a stack-allocated value.
break stepsLoop
case abiStepIntReg, abiStepPointer:
***
}
}
frameSize = align(frameSize, goarch.PtrSize)
frameSize += abi.spill
// Mark pointers in registers for the return path.
regArgs.ReturnIsPtr = abi.outRegPtrs
call(frametype, fn, args, uint32(frametype.size), uint32(retOffset))
x86架构实现 https://github.com/golang/go/blob/f5d494bbdf945f2662eb4da45cdb75de2b7d43d4/src/runtime/asm_amd64.s#L371
#define CALLFN(NAME,MAXSIZE) \
TEXT NAME(SB), WRAPPER, $MAXSIZE-32; \
NO_LOCAL_POINTERS; \
/* copy arguments to stack */ \
MOVQ argptr+16(FP), SI; \
MOVLQZX argsize+24(FP), CX; \
MOVQ SP, DI; \
REP;MOVSB; \
/* call function */ \
MOVQ f+8(FP), DX; \
PCDATA $PCDATA_StackMapIndex, $0; \
CALL (DX); \
/* copy return values back */ \
MOVQ argptr+16(FP), DI; \
MOVLQZX argsize+24(FP), CX; \
MOVLQZX retoffset+28(FP), BX; \
MOVQ SP, SI; \
ADDQ BX, DI; \
ADDQ BX, SI; \
SUBQ BX, CX; \
REP;MOVSB; \
/* execute write barrier updates */ \
MOVQ argtype+0(FP), DX; \
MOVQ argptr+16(FP), DI; \
MOVLQZX argsize+24(FP), CX; \
MOVLQZX retoffset+28(FP), BX; \
MOVQ DX, 0(SP); \
MOVQ DI, 8(SP); \
MOVQ CX, 16(SP); \
MOVQ BX, 24(SP); \
CALL runtime·callwritebarrier(SB); \
RET
处理返回值 当函数调用结束之后,就会开始处理函数的返回值:
if nout == 0 {
if stackArgs != nil {
typedmemclr(frametype, stackArgs)
framePool.Put(stackArgs)
}
} else {
if stackArgs != nil {
// Zero the now unused input area of args,
// because the Values returned by this function contain pointers to the args object,
// and will thus keep the args object alive indefinitely.
typedmemclrpartial(frametype, stackArgs, 0, abi.retOffset)
}
// Wrap Values around return values in args.
ret = make([]Value, nout)
for i := 0; i < nout; i++ {
tv := t.Out(i)
if tv.Size() == 0 {
// For zero-sized return value, args+off may point to the next object.
// In this case, return the zero value instead.
ret[i] = Zero(tv)
continue
}
steps := abi.ret.stepsForValue(i)
if st := steps[0]; st.kind == abiStepStack {
// This value is on the stack. If part of a value is stack
// allocated, the entire value is according to the ABI. So
// just make an indirection into the allocated frame.
fl := flagIndir | flag(tv.Kind())
ret[i] = Value{tv.common(), add(stackArgs, st.stkOff, "tv.Size() != 0"), fl}
// Note: this does introduce false sharing between results -
// if any result is live, they are all live.
// (And the space for the args is live as well, but as we've
// cleared that space it isn't as big a deal.)
continue
***
}
func Benchmark_Reflect_Field(b *testing.B) {
var tf = new(TestReflectField)
temp := reflect.ValueOf(tf).Elem()
for i := 0; i < b.N; i++ {
temp.Field(1).SetInt(int64(25))
}
_ = tf
}
// 测试反射设置结构体字段的性能
func Benchmark_Reflect_FieldByName(b *testing.B) {
var tf = new(TestReflectField)
temp := reflect.ValueOf(tf).Elem()
for i := 0; i < b.N; i++ {
temp.FieldByName("Age").SetInt(int64(25))
}
_ = tf
}
// 测试结构体字段设置的性能
func Benchmark_Field(b *testing.B) {
var tf = new(TestReflectField)
for i := 0; i < b.N; i++ {
tf.Age = i
}
_ = tf
}
通过反射与直接调用性能差距在10倍左右
func BenchmarkMethod(b *testing.B) {
t := &TestReflectField{}
for i := 0; i < b.N; i++ {
t.Func0()
}
}
// 测试通过序号反射访问无参数方法性能
func BenchmarkReflectMethod(b *testing.B) {
v := reflect.ValueOf(&TestReflectField{})
for i := 0; i < b.N; i++ {
v.Method(0).Call(nil)
}
}
// 测试通过名称反射访问无参数方法性能
func BenchmarkReflectMethodByName(b *testing.B) {
v := reflect.ValueOf(&TestReflectField{})
for i := 0; i < b.N; i++ {
v.MethodByName("Func0").Call(nil)
}
}
// 测试通过反射访问有参数方法性能
func BenchmarkReflectMethod_WithArgs(b *testing.B) {
v := reflect.ValueOf(&TestReflectField{})
for i := 0; i < b.N; i++ {
v.Method(1).Call([]reflect.Value{reflect.ValueOf(i)})
}
}
// 测试通过反射访问结构体参数方法性能
func BenchmarkReflectMethod_WithArgs_Mul(b *testing.B) {
v := reflect.ValueOf(&TestReflectField{})
for i := 0; i < b.N; i++ {
v.Method(2).Call([]reflect.Value{reflect.ValueOf(TestReflectField{})})
}
}
// 测试通过反射访问接口参数方法性能
func BenchmarkReflectMethod_WithArgs_Interface(b *testing.B) {
v := reflect.ValueOf(&TestReflectField{})
for i := 0; i < b.N; i++ {
var tf TestInterface = &TestReflectField{}
v.Method(3).Call([]reflect.Value{reflect.ValueOf(tf)})
}
}
// 测试访问多参数方法性能
func BenchmarkMethod_WithManyArgs(b *testing.B) {
s := &TestReflectField{}
for i := 0; i < b.N; i++ {
s.Func4(i, i, i, i, i, i)
}
}
// 测试通过反射访问多参数方法性能
func BenchmarkReflectMethod_WithManyArgs(b *testing.B) {
v := reflect.ValueOf(&TestReflectField{})
va := make([]reflect.Value, 0)
for i := 1; i <= 6; i++ {
va = append(va, reflect.ValueOf(i))
}
for i := 0; i < b.N; i++ {
v.Method(4).Call(va)
}
}
// 测试访问有返回值的方法性能
func BenchmarkMethod_WithResp(b *testing.B) {
s := &TestReflectField{}
for i := 0; i < b.N; i++ {
_ = s.Func5()
}
}
// 测试通过反射访问有返回值的方法性能
func BenchmarkReflectMethod_WithResp(b *testing.B) {
v := reflect.ValueOf(&TestReflectField{})
for i := 0; i < b.N; i++ {
_ = v.Method(5).Call(nil)[0].Int()
}
}
通过反射与正常方法调用性能差距在10倍左右
字段值设置
引用资料 https://go.dev/blog/laws-of-reflection Go 语言设计与实现 https://juejin.cn/post/6844903559796883463