1

编程语言

 2 years ago
source link: https://blogread.cn/it/article/8279?f=hot1
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Go Reflect 性能

浏览:352次  出处信息

   Go reflect包提供了运行时获取对象的类型和值的能力,它可以帮助我们实现代码的抽象和简化,实现动态的数据获取和方法调用, 提高开发效率和可读性, 也弥补Go在缺乏泛型的情况下对数据的统一处理能力。

   通过reflect,我们可以实现获取对象类型、对象字段、对象方法的能力,获取struct的tag信息,动态创建对象,对象是否实现特定的接口,对象的转换、对象值的获取和设置、Select分支动态调用等功能, 看起来功能不错,但是大家也都知道一点: 使用reflect是有性能代价的!

   Java中的reflect的使用对性能也有影响, 但是和Java reflect不同, Java中不区分TypeValue类型的, 所以至少Java中我们可以预先讲相应的reflect对象缓存起来,减少反射对性能的影响, 但是Go没办法预先缓存reflect, 因为Type类型并不包含对象运行时的值,必须通过ValueOf和运行时实例对象才能获取Value对象。

   对象的反射生成和获取都会增加额外的代码指令, 并且也会涉及interface{}装箱/拆箱操作,中间还可能增加临时对象的生成,所以性能下降是肯定的,但是具体能下降多少呢,还是得数据来说话。

   当然,不同的reflect使用的姿势, 以及对象类型的不同,都会多多少少影响性能的测试数据,我们就以一个普通的struct类型为例:

package test
import (
"reflect"
"testing"
type Student struct {
Name  string
Age   int
Class string
Score int
func BenchmarkReflect_New(b *testing.B) {
var s *Student
sv := reflect.TypeOf(Student{})
b.ResetTimer()
for i := 0; i < b.N; i++ {
sn := reflect.New(sv)
s, _ = sn.Interface().(*Student)
func BenchmarkDirect_New(b *testing.B) {
var s *Student
b.ResetTimer()
for i := 0; i < b.N; i++ {
s = new(Student)
func BenchmarkReflect_Set(b *testing.B) {
var s *Student
sv := reflect.TypeOf(Student{})
b.ResetTimer()
for i := 0; i < b.N; i++ {
sn := reflect.New(sv)
s = sn.Interface().(*Student)
s.Name = "Jerry"
s.Age = 18
s.Class = "20005"
s.Score = 100
func BenchmarkReflect_SetFieldByName(b *testing.B) {
sv := reflect.TypeOf(Student{})
b.ResetTimer()
for i := 0; i < b.N; i++ {
sn := reflect.New(sv).Elem()
sn.FieldByName("Name").SetString("Jerry")
sn.FieldByName("Age").SetInt(18)
sn.FieldByName("Class").SetString("20005")
sn.FieldByName("Score").SetInt(100)
func BenchmarkReflect_SetFieldByIndex(b *testing.B) {
sv := reflect.TypeOf(Student{})
b.ResetTimer()
for i := 0; i < b.N; i++ {
sn := reflect.New(sv).Elem()
sn.Field(0).SetString("Jerry")
sn.Field(1).SetInt(18)
sn.Field(2).SetString("20005")
sn.Field(3).SetInt(100)
func BenchmarkDirect_Set(b *testing.B) {
var s *Student
b.ResetTimer()
for i := 0; i < b.N; i++ {
s = new(Student)
s.Name = "Jerry"
s.Age = 18
s.Class = "20005"
s.Score = 100

   测试结果:

BenchmarkReflect_New-4               20000000   70.0 ns/op     48 B/op       1 allocs/op
BenchmarkDirect_New-4                30000000   45.6 ns/op     48 B/op       1 allocs/op
BenchmarkReflect_Set-4               20000000   73.6 ns/op     48 B/op       1 allocs/op
BenchmarkReflect_SetFieldByName-4     3000000   492 ns/op     80 B/op       5 allocs/op
BenchmarkReflect_SetFieldByIndex-4   20000000   111 ns/op     48 B/op       1 allocs/op
BenchmarkDirect_Set-4                   30000000   43.1 ns/op     48 B/op       1 allocs/op

   我们进行了两种功能的测试:

  • 对象(struct)的创建

  • 对象字段的赋值

   对于对象的创建,通过反射生成对象需要 70纳秒, 而直接new这个对象却只需要45.6纳秒, 性能差别还是很大的。

   对于字段的赋值,一共四个测试用例:

  • Reflect_Set: 通过反射生成对象,并将这个对象转换成实际的对象,直接调用对象的字段进行赋值, 需要73.6纳秒

  • Reflect_SetFieldByName: 通过反射生成对象,通过FieldByName进行赋值, 需要492纳秒

  • Reflect_SetFieldByIndex: 通过反射生成对象,通过Field进行赋值, 需要111纳秒

  • Direct_Set: 直接调用对象的字段进行赋值, 只需要43.1纳秒

   Reflect_SetDirect_Set性能的主要差别还是在于对象的生成,因为之后字段的赋值方法都是一样的,这也和对象创建的测试case的结果是一致的。

   如果通过反射进行赋值,性能下降是很厉害的,耗时成倍的增长。比较有趣的是,FieldByName方式赋值是Field方式赋值的好几倍, 原因在于FieldByName会有额外的循环进行字段的查找,虽然最终它还是调用Field进行赋值:

func (v Value) FieldByName(name string) Value {
v.mustBe(Struct)
if f, ok := v.typ.FieldByName(name); ok {
return v.FieldByIndex(f.Index)
return Value{}
func (v Value) FieldByIndex(index []int) Value {
if len(index) == 1 {
return v.Field(index[0])
v.mustBe(Struct)
for i, x := range index {
if i > 0 {
if v.Kind() == Ptr && v.typ.Elem().Kind() == Struct {
if v.IsNil() {
panic("reflect: indirection through nil pointer to embedded struct")
v = v.Elem()
v = v.Field(x)
return v

   从上面的测试结果看, 通过反射生成对象和字段赋值都会影响性能,但是通过反射的确确确实实能简化代码,为业务逻辑提供统一的代码, 比如标准库中json的编解码、rpc服务的注册和调用, 一些ORM框架比如gorm等,都是通过反射处理数据的,这是为了能处理通用的类型。

https://github.com/golang/go/blob/master/src/encoding/json/decode.go#L946
 ......
     case reflect.String:
v.SetString(string(s))
case reflect.Interface:
if v.NumMethod() == 0 {
v.Set(reflect.ValueOf(string(s)))
} else {
d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.readIndex())})
 ......
https://github.com/jinzhu/gorm/blob/master/scope.go#L495
    for fieldIndex, field := range selectFields {
if field.DBName == column {
if field.Field.Kind() == reflect.Ptr {
values[index] = field.Field.Addr().Interface()
} else {
reflectValue := reflect.New(reflect.PtrTo(field.Struct.Type))
reflectValue.Elem().Set(field.Field.Addr())
values[index] = reflectValue.Interface()
resetFields[index] = field
selectedColumnsMap[column] = offset + fieldIndex
if field.IsNormal {
break

   在我们追求高性能的场景的时候,我们可能需要尽量避免反射的调用, 比如对json数据的unmarshal, easyjson就通过生成器的方式,避免使用反射。

func (v *Student) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson4a74e62dDecodeGitABCReflect(&r, v)
return r.Error()
func (v *Student) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson4a74e62dDecodeGitABCReflect(l, v)
func easyjson4a74e62dDecodeGitABCReflect(in *jlexer.Lexer, out *Student) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
in.Skip()
return
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeString()
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
switch key {
case "Name":
out.Name = string(in.String())
case "Age":
out.Age = int(in.Int())
case "Class":
out.Class = string(in.String())
case "Score":
out.Score = int(in.Int())
default:
in.SkipRecursive()
in.WantComma()
in.Delim('}')
if isTopLevel {
in.Consumed()

   其它的一些编解码库也提供了这种避免使用反射的方法来提高性能。

顺带测一下"装箱/拆箱"操作带来的性能影响

func DirectInvoke(s *Student) {
s.Name = "Jerry"
s.Age = 18
s.Class = "20005"
s.Score = 100
func InterfaceInvoke(i interface{}) {
s := i.(*Student)
s.Name = "Jerry"
s.Age = 18
s.Class = "20005"
s.Score = 100
func BenchmarkDirectInvoke(b *testing.B) {
s := new(Student)
for i := 0; i < b.N; i++ {
DirectInvoke(s)
func BenchmarkInterfaceInvoke(b *testing.B) {
s := new(Student)
for i := 0; i < b.N; i++ {
InterfaceInvoke(s)

   测试结果:

BenchmarkDirectInvoke-4              300000000 5.60 ns/op       0 B/op       0 allocs/op
BenchmarkInterfaceInvoke-4           200000000 6.64 ns/op       0 B/op       0 allocs/op

   可以看到将具体对象转换成 interface{}(以及反向操作)确实回带来一点点性能的影响,不过看起来影响倒不是很大。

建议继续学习:

QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK