golang的reflect

栏目: Go · 发布时间: 5年前

内容简介:在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。每种语言的反射模型都不同,并且有些语言根本不支持反射。Golang语言实现了反射,反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含这个包就可以使用。Golang的gRPC,json包都是通过反射实

编程语言中反射的概念

在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

每种语言的反射模型都不同,并且有些语言根本不支持反射。Golang语言实现了反射,反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含这个包就可以使用。

Golang的gRPC,json包都是通过反射实现的。

interface 和 反射

interface的wiki计划整理到这里: golang的interface

golang的变量分为两部分,type和value,value用的是指针word,type是rtype或者itab表示(itab是运行时动态生成的虚表)。itab主要是用来表示有方法的type的。

itab包含两个rtype,分别是static type和concrete type,而我们在interface类型断言中用到的是concrete type。

static type一般与golang的内置类型相关是创建变量时可以确定的,concrete type一般与用户定义的interface类型相关。

在实现时,golang的类型有通过接口Type和结构体rtype来定义,因为没有继承的概念,所以所以代码中都通过 *rtype这个“基类”来传递,实际使用的时候,通过t.Kind()判断rtype的类型,通过unsafe.Pointer把rtype转换为对应的Type的实现。

golang中反射的reflect.TypeOf(interface{})方法就可以获取Type类型,其具体实现如下:

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
   eface := *(*emptyInterface)(unsafe.Pointer(&i)) //传入前已经有一次饮食类型转换把接口转换为空接口类型,src/runtime/iface.go中有隐式转换的代码。
   return toType(eface.typ)
}

// toType converts from a *rtype to a Type that can be returned
// to the client of package reflect. In gc, the only concern is that
// a nil *rtype must be replaced by a nil Type, but in gccgo this
// function takes care of ensuring that multiple *rtype for the same
// type are coalesced into a single Type.
func toType(t *rtype) Type {
   if t == nil {
      return nil
   }
   return t
}

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))
      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")
}
//src/runtime/iface.go
func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
   if raceenabled {
      raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E))
   }
   if msanenabled {
      msanread(elem, t.size)
   }
   x := mallocgc(t.size, t, true)
   // TODO: We allocate a zeroed object only to overwrite it with actual data.
   // Figure out how to avoid zeroing. Also below in convT2Eslice, convT2I, convT2Islice.
   typedmemmove(t, x, elem)
   e._type = t
   e.data = x
   return
}

没有找到从iface到eface的转换的完整过程,不过从_type,unsafe.Pointer到eface的转换应该包含了内存的分配和拷贝,这部分对于执行耗时的影响不大,只是可能会增大GC的压力。

断言的性能分析

先附上网上的一篇博客, https://blog.csdn.net/erlib/article/details/24197069 。尝试对博客的测试进行细化。

首先在 go 1.10.2下更新下测试结果,从中可以看到switch带来的性能损耗在均值下还是存在的(虚表比较?约等于类型断言?),然后测试发现v interface{} 作为接收参数时,不会发生参数转换。

$ go test -test.bench=".*"  ./reflect_benchmark_test.go
goos: darwin
goarch: amd64
Benchmark_TypeSwitch-4          100000000               19.6 ns/op
Benchmark_NormalSwitch-4        2000000000               1.69 ns/op
Benchmark_InterfaceSwitch-4     100000000               11.7 ns/op
Benchmark_InterfaceIn-4         2000000000               1.58 ns/op
PASS
ok      command-line-arguments  10.055s

之后看下真正耗时的部分,也就是类型断言的代码,其中t.find执行了两遍,在未上锁执行了一遍,上锁又执行了一遍,测试发现时间影响确实不大,这样可以有效避免并发时对interface的修改?

func assertI2I(inter *interfacetype, i iface) (r iface) {
   tab := i.tab
   if tab == nil {
      // explicit conversions require non-nil interface value.
      panic(&TypeAssertionError{"", "", inter.typ.string(), ""})
   }
   if tab.inter == inter {
      r.tab = tab
      r.data = i.data
      return
   }
   r.tab = getitab(inter, tab._type, false)
   r.data = i.data
   return
}

func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
   if len(inter.mhdr) == 0 {
      throw("internal error - misuse of itab")
   }

   // easy case
   if typ.tflag&tflagUncommon == 0 {
      if canfail {
         return nil
      }
      name := inter.typ.nameOff(inter.mhdr[0].name)
      panic(&TypeAssertionError{"", typ.string(), inter.typ.string(), name.name()})
   }

   var m *itab

   // First, look in the existing table to see if we can find the itab we need.
   // This is by far the most common case, so do it without locks.
   // Use atomic to ensure we see any previous writes done by the thread
   // that updates the itabTable field (with atomic.Storep in itabAdd).
   t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
   if m = t.find(inter, typ); m != nil {
      goto finish
   }

   // Not found.  Grab the lock and try again.
   lock(&itabLock)
   if m = itabTable.find(inter, typ); m != nil {
      unlock(&itabLock)
      goto finish
   }

   // Entry doesn't exist yet. Make a new entry & add it.
   m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
   m.inter = inter
   m._type = typ
   m.init()
   itabAdd(m)
   unlock(&itabLock)
finish:
   if m.fun[0] != 0 {
      return m
   }
   if canfail {
      return nil
   }
   // this can only happen if the conversion
   // was already done once using the , ok form
   // and we have a cached negative result.
   // The cached result doesn't record which
   // interface function was missing, so initialize
   // the itab again to get the missing function name.
   panic(&TypeAssertionError{concreteString: typ.string(), assertedString: inter.typ.string(), missingMethod: m.init()})
}

// find finds the given interface/type pair in t.
// Returns nil if the given interface/type pair isn't present.
func (t *itabTableType) find(inter *interfacetype, typ *_type) *itab {
   // Implemented using quadratic probing.
   // Probe sequence is h(i) = h0 + i*(i+1)/2 mod 2^k.
   // We're guaranteed to hit all table entries using this probe sequence.
   mask := t.size - 1
   h := itabHashFunc(inter, typ) & mask
   for i := uintptr(1); ; i++ {
      p := (**itab)(add(unsafe.Pointer(&t.entries), h*sys.PtrSize))
      // Use atomic read here so if we see m != nil, we also see
      // the initializations of the fields of m.
      // m := *p
      m := (*itab)(atomic.Loadp(unsafe.Pointer(p)))
      if m == nil {
         return nil
      }
      if m.inter == inter && m._type == typ {
         return m
      }
      h += i
      h &= mask
   }
}

从代码和流程来分析,以上基本包含了反射的基本流程,拿到一个Type接口的实现,之后根据这个Type类型再做的操作就没有特别耗时的了。

从代码可以看出可能存在的耗时主要在两方面,

1.大量值传递带来的gc压力(这个还不知道如何去分析所占的权重)

2.itab比较时,比较耗时。(这个根源是虚表是运行时动态生成的,interface接口继承关系太松散导致无法编译时解析?)

从reflect三法则看反射的用法:

从以下三条法则中,就可以看到反射的基本用法,具体可以自行仔细研究,本质都是基于Type接口的操作。

1.从接口值到反射对象的反射

2.从反射对象到接口值的反射

3.为了修改反射对象,其值必须可设置


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

大数据供应链

大数据供应链

娜达·R·桑德斯 (Nada R. Sanders) / 丁晓松 / 中国人民大学出版社 / 2015-7-1 / CNY 55.00

第一本大数据供应链落地之道的权威著作,全球顶级供应链管理专家娜达·桑德斯博士聚焦传统供应链模式向大数据转型,助力工业4.0时代智能供应链构建。 在靠大数据驱动供应链处于领先地位的企业中,45% 是零售商,如沃尔玛、亚马逊,而22%是快消企业,如戴尔电脑。他们都前所未有地掌控了自己的供应链。在库存管理、订单履行率、原材料和产品交付上具有更为广阔的视野。利用具有预见性的大数据分析结果,可以使供需......一起来看看 《大数据供应链》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

URL 编码/解码
URL 编码/解码

URL 编码/解码

SHA 加密
SHA 加密

SHA 加密工具