学习go底层:接口与反射

reflect

reflect的typeof()有什么用?

reflect的typeof函数是可以动态的获取结构体的名字。

那么是typeof是如何动态的获取结构体的名字?

其获取结构体名字的代码如下:

//main
type sunshine struct{
  Light int
}

func main() {
  _ :=reflect.TypeOf(sunshine{})
}


//go1.13.4:reflect/value.go
func TypeOf(i interface{}) Type {
  eface := *(*emptyInterface)(unsafe.Pointer(&i))
  return toType(eface.typ)
}
  1. 首先typeof函数会把自定义的struct赋值给接口,然后对该接口类型转化为一个emptyInterface指针

  2. 然后通过emptyInterface指针中的typ获取来获取其中结构体的名字

为什么可以将interface转换为emptyInterface?

原因在于interface接口的内部实现与emptyInterface的实现是一样的,interface具体的内部实现如下:

//go1.13.4:runtime/runtime2.go     //go1.13.4:reflect/value.go
//接口的内部实现
//带方法的接口
type iface struct {
  tab *itab
  data unsafe.Pointer
}

//不带方法的空接口                    //emptyInterface的结构
type eface struct {                type emptyInterface struct{
  _type *_type                       typ *rtype
  data unsafe.Pointer                word unsafe.Pointer
}                                  }

其中_type和的rtype具体构造如下:

//go1.13.4:runtime/type.go       //go1.13.4:reflect/type.go

type _type struct {              type rtype struct {
  size    uintptr                size    uintptr
  ptrdata  uintptr               ptrdata  uintptr
  hash    uint32                 hash    uint32
  tflag   tflag                  tflag   tflag
  align   uint8                  align   uint8
  fieldalign uint8               fieldAlign uint8
  kind    uint8                  kind    uint8
  alg    *typeAlg                alg    *typeAlg
  gcdata  *byte                  gcdata   *byte
  str    nameOff                 str    nameOff
  ptrToThis typeOff              ptrToThis typeOff
}                                }

由此可见,emptyInterface是通过构造和空接口内部相同的数据结构来进行赋值的。这样reflect包就获取到的空接口内部的所有信息。

而sunshine是如何赋值给interface的呢?

为了对代码进行分析,使用以下代码来进行编译,观察runtime是如何将sunshine结构体赋值给interface的。

type sunshine struct{
  Light int64
  Name string
}

type inter interface {}

func main(){
  var sun sunshine
  var i inter
  i = sun
  fmt.Println(i)
}

其中只观察 i = sun 这一行代码是如何进行编译的(汇编语言:plan9汇编):

  #以下为i = sun的部分汇编
  ...
  MOVQ  AX, 8(SP)
  CALL  runtime.convT2E(SB)
  PCDATA $0, $1
  ...

此处的赋值操作是通过调用runtime的convT2E来实现赋值的操作的。而convT2E的实现如下:

//runtime/iface.go
// i = sun 所对应的操作
1  func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
2  if raceenabled {
3    raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E))
4  }
5  if msanenabled {
6    msanread(elem, t.size)
7  }
8  x := mallocgc(t.size, t, true)
9  typedmemmove(t, x, elem)
10 e._type = t
11 e.data = x
12 return
13}

入参elem:实际上是一个指针指向sun这个变量

入参t:记录关于elem所指向的数据的所有的信息

那么t的数据信息是从哪里的来的?

实际t中的数据信息在编译时就已经构建好的,然后当运行到i = sun时,只需要将信息提取出来放入到t中。如t中的nameOff,align等。

并且在第8行会对elem的数据进行一次拷贝。然后将拷贝后的副本赋值给接口。所以如果当赋值给interface的数据本身很庞大,将会极大的增加gc的负担。

现在我们回过头来看下_type的具体含义:

type _type struct {
  size    uintptr //数据的大小
  ptrdata  uintptr //数据中包含指针的大小
  hash    uint32  //类型的hash值
  tflag   tflag  //一些额外的标识,包含tflagNamed:是否有名字,tflagExtraStar:是否有*号, tflagUncommon:是否包含非一般类型
  align   uint8  //对齐的字节数
  fieldalign uint8  //结构体的对齐字节数
  kind    uint8  //指向的数据的类型
  alg    *typeAlg //操作函数
  gcdata  *byte   //垃圾收集器
  str    nameOff  //该_type所对应的数据的名字的偏移
  ptrToThis typeOff  //该_type所在内存中的偏移
}

所以对应interface的类型对应的数据结构如下:

interface结构
type *rtype
ptr unsafe.Pointer

其中type记录的ptr指向的数据的相关信息,而ptr则指向对应结构的数据。当对interface进行赋值时会拷贝一份值的数据,然后ptr指向的是其拷贝的数据。

现在回过头来看reflect.TypeOf函数

>typeof函数实际上是返回接口中type中的相关数据,而type相关的数据由于在reflect中定义为私有的。所以只能通过其开放出来的函数进行间接的获取相关的数据。

reflect.ValueOf函数

valueof函数和typeof很相似,实际上是返回是接口的type和ptr数据以及flag。包装在Value中。

下面我们来看下Value.NumField函数:

//reflect/value.go
func (v Value) NumField() int {
  v.mustBe(Struct)    //用来判断value所对应的值是一个结构体
  tt := (*structType)(unsafe.Pointer(v.typ)) //将value中的type强转为structType
  return len(tt.fields)  //返回tt中fields的个数
}

这里存在一个问题,就是为什么type可以强制转换为structType类型。

原因在于type的内存模型实际上是如下(内存1和内存2和内存3是连续的):

内存 1 内存2 内存3
rtype pkgPath fields

即当指针为type时,则只会获取到rtype相关的数据,事实上,pkgPath和fields也是在编译时设置好了的。

所以可以直接进行转换。

总结

  1. 对于接口interface的赋值会拷贝一份数据到interface中,所以如果结构体本身十分的大并且频繁的进行对接口赋值,这会在这中间产生大量的拷贝从而影响性能。
  2. 而对于反射来获取结构体信息实际上是从runtime中获取结构体的元信息,其实现其实是通过指针加偏移量来找到数据。所以对于获取信息本身不是很消耗性能。
  3. 由此可见,对于反射性能低的原因主要由以上2点导致的
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,295评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,928评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,682评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,209评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,237评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,965评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,586评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,487评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,016评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,136评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,271评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,948评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,619评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,139评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,252评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,598评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,267评论 2 358

推荐阅读更多精彩内容