提高 golang 的反射性能

golang 的反射很慢。这个和它的 api 设计有关。在 java 里面,我们一般使用反射都是这样来弄的。


Field field = clazz.getField("hello");

field.get(obj1);

field.get(obj2);


这个取得的反射对象类型是 java.lang.reflect.Field。它是可以复用的。只要传入不同的obj,就可以取得这个obj上对应的 field。但是 golang 的反射不是这样设计的


type_ := reflect.TypeOf(obj)

field, _ := type_.FieldByName("hello")


这里取出来的 field 对象是 reflect.StructField 类型,但是它没有办法用来取得对应对象上的值。如果要取值,得用另外一套对object,而不是type的反射


type_ := reflect.ValueOf(obj)

fieldValue := type_.FieldByName("hello")


这里取出来的 fieldValue 类型是 reflect.Value,它是一个具体的值,而不是一个可复用的反射对象了。

这就很蛋疼了!每次反射都需要malloc这个reflect.Value结构体。golang的反射性能怎么可能快?

Jsoniter 是 golang 实现的,基于反射的 JSON 解析器。其实现原理是用 reflect.Type 得出来的信息来直接做反射,而不依赖于 reflect.ValueOf。具体是怎么实现的呢?

结构体

先解决一个小问题。怎么利用 reflect.StructField 取得对象上的值?

对应的代码在: go/feature_reflect_object.go at master · json-iterator/go · GitHub

fieldPtr := uintptr(structPtr) + field.Offset

在 reflect.StructField 上有一个 Offset 的属性。利用这个可以计算出字段的指针值。我们可以写一个小测试来验证,这个是对的。


type TestObj struct {

field1 string

}

struct_ := &TestObj{}

field, _ := reflect.TypeOf(struct_).Elem().FieldByName("field1")

field1Ptr := uintptr(unsafe.Pointer(struct_)) + field.Offset

*((*string)(unsafe.Pointer(field1Ptr))) = "hello"

fmt.Println(struct_)


打印出来的消息是 &{hello}

获取 interface{} 的指针

如果对应的结构体是以 interface{} 传进来的。还需要从 interface{} 上取得结构体的指针


type TestObj struct {

field1 string

}

struct_ := &TestObj{}

structInter := (interface{})(struct_)

// emptyInterface is the header for an interface{} value.

type emptyInterface struct {

typ *struct{}

word unsafe.Pointer

}

structPtr := (*emptyInterface)(unsafe.Pointer(&structInter)).word

field, _ := reflect.TypeOf(structInter).Elem().FieldByName("field1")

field1Ptr := uintptr(structPtr) + field.Offset

*((*string)(unsafe.Pointer(field1Ptr))) = "hello"

fmt.Println(struct_)


Slice

搞定了结构体,接下来就是处理slice类型了。

对应的代码在:go/feature_reflect_array.go at master · json-iterator/go · GitHub


type sliceHeader struct {

Data unsafe.Pointer

Len int

Cap int

}


slice 的秘诀在于取出指向数组头部的指针,然后具体的元素,通过偏移量来计算。


slice := []string{"hello", "world"}

type sliceHeader struct {

Data unsafe.Pointer

Len int

Cap int

}

header := (*sliceHeader)(unsafe.Pointer(&slice))

fmt.Println(header.Len)

elementType := reflect.TypeOf(slice).Elem()

secondElementPtr := uintptr(header.Data) + elementType.Size()

*((*string)(unsafe.Pointer(secondElementPtr))) = "!!!"

fmt.Println(slice)


打印出来的内容


2

[hello !!!]

Map


对于 Map 类型来说,没有 reflect.ValueOf 之外的获取其内容的方式。所以还是只能老老实实地用golang自带的值反射api。

本文分享自微信公众号 - Golang语言社区(Golangweb)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-02-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏图形学与OpenGL

WebGL三角形旋转变换

36210
来自专栏码匠的流水账

聊聊storm的WindowedBolt

storm-2.0.0/storm-client/src/jvm/org/apache/storm/topology/IWindowedBolt.java

9720
来自专栏一个会写诗的程序员的博客

禅与 JavaScript 编程艺术, Zen and The Art of JavaScript Programming禅与 JavaScript 编程艺术

Zen and The Art of JavaScript Programming

13710
来自专栏算法修养

HYSBZ 3676 回文串 (回文树)

3676: [Apio2014]回文串 Time Limit: 20 Sec  Memory Limit: 128 MB Submit: 1680  Solve...

38270
来自专栏Hongten

python开发_calendar

如果你用过linux,你可能知道在linux下面的有一个强大的calendar功能,即日历

14920
来自专栏更流畅、简洁的软件开发方式

[自定义服务器控件] 第三步:CheckBoxList。

前面发了文本框和下拉列表框的,这回发一个CheckBoxList。不知道中文名字该叫什么。 CheckBoxList 最郁闷的地方就是:明明可以选择多个选项,但...

24260
来自专栏菩提树下的杨过

无限级分类(非递归算法/存储过程版/GUID主键)完整数据库示例_(2)插入记录

-- ======================================== -- Author:  <杨俊明,jimmy.yang@cntvs.c...

21690
来自专栏GIS讲堂

js实现城市首字母导航

13410
来自专栏SeanCheney的专栏

《Pandas Cookbook》第09章 合并Pandas对象

29510
来自专栏函数式编程语言及工具

SDP(9):MongoDB-Scala - data access and modeling

    MongoDB是一种文件型数据库,对数据格式没有硬性要求,所以可以实现灵活多变的数据存储和读取。MongoDB又是一种分布式数据库,与传统关系数据库不同...

41040

扫码关注云+社区

领取腾讯云代金券