Go reflect包提供了运行时获取对象的类型和值的能力,它可以帮助我们实现代码的抽象和简化,实现动态的数据获取和方法调用, 提高开发效率和可读性, 也弥补Go在缺乏泛型的情况下对数据的统一处理能力。
通过reflect,我们可以实现获取对象类型、对象字段、对象方法的能力,获取struct的tag信息,动态创建对象,对象是否实现特定的接口,对象的转换、对象值的获取和设置、Select分支动态调用等功能, 看起来功能不错,但是大家也都知道一点: 使用reflect是有性能代价的!
测试
TypeValueTypeValueOfValue
interface{}
当然,不同的reflect使用的姿势, 以及对象类型的不同,都会多多少少影响性能的测试数据,我们就以一个普通的struct类型为例:
1package test
2import (
3 "reflect"
4 "testing"
5)
6type Student struct {
7 Name string
8 Age int
9 Class string
10 Score int
11}
12func BenchmarkReflect_New(b *testing.B) {
13 var s *Student
14 sv := reflect.TypeOf(Student{})
15 b.ResetTimer()
16 for i := 0; i < b.N; i++ {
17 sn := reflect.New(sv)
18 s, _ = sn.Interface().(*Student)
19 }
20 _ = s
21}
22func BenchmarkDirect_New(b *testing.B) {
23 var s *Student
24 b.ResetTimer()
25 for i := 0; i < b.N; i++ {
26 s = new(Student)
27 }
28 _ = s
29}
30func BenchmarkReflect_Set(b *testing.B) {
31 var s *Student
32 sv := reflect.TypeOf(Student{})
33 b.ResetTimer()
34 for i := 0; i < b.N; i++ {
35 sn := reflect.New(sv)
36 s = sn.Interface().(*Student)
37 s.Name = "Jerry"
38 s.Age = 18
39 s.Class = "20005"
40 s.Score = 100
41 }
42}
43func BenchmarkReflect_SetFieldByName(b *testing.B) {
44 sv := reflect.TypeOf(Student{})
45 b.ResetTimer()
46 for i := 0; i < b.N; i++ {
47 sn := reflect.New(sv).Elem()
48 sn.FieldByName("Name").SetString("Jerry")
49 sn.FieldByName("Age").SetInt(18)
50 sn.FieldByName("Class").SetString("20005")
51 sn.FieldByName("Score").SetInt(100)
52 }
53}
54func BenchmarkReflect_SetFieldByIndex(b *testing.B) {
55 sv := reflect.TypeOf(Student{})
56 b.ResetTimer()
57 for i := 0; i < b.N; i++ {
58 sn := reflect.New(sv).Elem()
59 sn.Field(0).SetString("Jerry")
60 sn.Field(1).SetInt(18)
61 sn.Field(2).SetString("20005")
62 sn.Field(3).SetInt(100)
63 }
64}
65func BenchmarkDirect_Set(b *testing.B) {
66 var s *Student
67 b.ResetTimer()
68 for i := 0; i < b.N; i++ {
69 s = new(Student)
70 s.Name = "Jerry"
71 s.Age = 18
72 s.Class = "20005"
73 s.Score = 100
74 }
75}
测试结果:
1BenchmarkReflect_New-4 20000000 70.0 ns/op 48 B/op 1 allocs/op
2BenchmarkDirect_New-4 30000000 45.6 ns/op 48 B/op 1 allocs/op
3BenchmarkReflect_Set-4 20000000 73.6 ns/op 48 B/op 1 allocs/op
4BenchmarkReflect_SetFieldByName-4 3000000 492 ns/op 80 B/op 5 allocs/op
5BenchmarkReflect_SetFieldByIndex-4 20000000 111 ns/op 48 B/op 1 allocs/op
6BenchmarkDirect_Set-4 30000000 43.1 ns/op 48 B/op 1 allocs/op
测试结果
我们进行了两种功能的测试:
对象(struct)的创建
对象字段的赋值
7045.6
对于字段的赋值,一共四个测试用例:
73.6FieldByName492Field11143.1
Reflect_SetDirect_Set
FieldByNameFieldFieldByNameField
1func (v Value) FieldByName(name string) Value {
2 v.mustBe(Struct)
3 if f, ok := v.typ.FieldByName(name); ok {
4 return v.FieldByIndex(f.Index)
5 }
6 return Value{}
7}
8func (v Value) FieldByIndex(index []int) Value {
9 if len(index) == 1 {
10 return v.Field(index[0])
11 }
12 v.mustBe(Struct)
13 for i, x := range index {
14 if i > 0 {
15 if v.Kind() == Ptr && v.typ.Elem().Kind() == Struct {
16 if v.IsNil() {
17 panic("reflect: indirection through nil pointer to embedded struct")
18 }
19 v = v.Elem()
20 }
21 }
22 v = v.Field(x)
23 }
24 return v
25}
优化
从上面的测试结果看, 通过反射生成对象和字段赋值都会影响性能,但是通过反射的确确确实实能简化代码,为业务逻辑提供统一的代码, 比如标准库中json的编解码、rpc服务的注册和调用, 一些ORM框架比如gorm等,都是通过反射处理数据的,这是为了能处理通用的类型。
1 ......
2 case reflect.String:
3 v.SetString(string(s))
4case reflect.Interface:
5 if v.NumMethod() == 0 {
6 v.Set(reflect.ValueOf(string(s)))
7 } else {
8 d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.readIndex())})
9 }
10 ......
1 for fieldIndex, field := range selectFields {
2if field.DBName == column {
3 if field.Field.Kind() == reflect.Ptr {
4 values[index] = field.Field.Addr().Interface()
5 } else {
6 reflectValue := reflect.New(reflect.PtrTo(field.Struct.Type))
7 reflectValue.Elem().Set(field.Field.Addr())
8 values[index] = reflectValue.Interface()
9 resetFields[index] = field
10 }
11 selectedColumnsMap[column] = offset + fieldIndex
12 if field.IsNormal {
13 break
14 }
15}
16 }
在我们追求高性能的场景的时候,我们可能需要尽量避免反射的调用, 比如对json数据的unmarshal, easyjson就通过生成器的方式,避免使用反射。
1func (v *Student) UnmarshalJSON(data []byte) error {
2 r := jlexer.Lexer{Data: data}
3 easyjson4a74e62dDecodeGitABCReflect(&r, v)
4 return r.Error()
5}
6func (v *Student) UnmarshalEasyJSON(l *jlexer.Lexer) {
7 easyjson4a74e62dDecodeGitABCReflect(l, v)
8}
9func easyjson4a74e62dDecodeGitABCReflect(in *jlexer.Lexer, out *Student) {
10 isTopLevel := in.IsStart()
11 if in.IsNull() {
12 if isTopLevel {
13 in.Consumed()
14 }
15 in.Skip()
16 return
17 }
18 in.Delim('{')
19 for !in.IsDelim('}') {
20 key := in.UnsafeString()
21 in.WantColon()
22 if in.IsNull() {
23 in.Skip()
24 in.WantComma()
25 continue
26 }
27 switch key {
28 case "Name":
29 out.Name = string(in.String())
30 case "Age":
31 out.Age = int(in.Int())
32 case "Class":
33 out.Class = string(in.String())
34 case "Score":
35 out.Score = int(in.Int())
36 default:
37 in.SkipRecursive()
38 }
39 in.WantComma()
40 }
41 in.Delim('}')
42 if isTopLevel {
43 in.Consumed()
44 }
45}
其它的一些编解码库也提供了这种避免使用反射的方法来提高性能。
顺带测一下"装箱/拆箱"操作带来的性能影响
1func DirectInvoke(s *Student) {
2 s.Name = "Jerry"
3 s.Age = 18
4 s.Class = "20005"
5 s.Score = 100
6}
7func InterfaceInvoke(i interface{}) {
8 s := i.(*Student)
9 s.Name = "Jerry"
10 s.Age = 18
11 s.Class = "20005"
12 s.Score = 100
13}
14func BenchmarkDirectInvoke(b *testing.B) {
15 s := new(Student)
16 for i := 0; i < b.N; i++ {
17 DirectInvoke(s)
18 }
19 _ = s
20}
21func BenchmarkInterfaceInvoke(b *testing.B) {
22 s := new(Student)
23 for i := 0; i < b.N; i++ {
24 InterfaceInvoke(s)
25 }
26 _ = s
27}
测试结果:
1BenchmarkDirectInvoke-4 300000000 5.60 ns/op 0 B/op 0 allocs/op
2BenchmarkInterfaceInvoke-4 200000000 6.64 ns/op 0 B/op 0 allocs/op
可以看到将具体对象转换成 interface{}(以及反向操作)确实回带来一点点性能的影响,不过看起来影响倒不是很大。
Golang语言社区
ID:Golangweb
www.bytedancing.com
游戏服务器架构丨分布式技术丨大数据丨游戏算法学习