golang反射(reflect)虽爽,但很贵

2023-12-13 12:46:35

标准库 reflect 为 Go 语言提供了运行时动态获取对象的类型和值以及动态创建对象的能力。反射可以帮助抽象和简化代码,提高开发效率。

但是使用反射势必会多出大量的操作指令,导致性能下降

案例

字段赋值方式对比

 type Student struct {
     Name   string
     Id     int32
     Addr   string
     Number string
 }
 ?
 func BenchmarkSetStudentValue(b *testing.B) {
     student := new(Student)
     b.ResetTimer()
     for i := 0; i < b.N; i++ {
         student.Name = "张三"
         student.Id = 10
         student.Addr = "缅甸"
         student.Number = "10086"
     }
 }
 ?
 func BenchmarkSetStudentValueByField(b *testing.B) {
     typ := reflect.TypeOf(Student{})
     v := reflect.New(typ).Elem()
     b.ResetTimer()
     for i := 0; i < b.N; i++ {
         v.Field(0).SetString("张三")
         v.Field(1).SetInt(10)
         v.Field(2).SetString("缅甸")
         v.Field(3).SetString("10086")
     }
 }
 ?
 func BenchmarkSetStudentValueByFieldName(b *testing.B) {
     typ := reflect.TypeOf(Student{})
     v := reflect.New(typ).Elem()
     b.ResetTimer()
     for i := 0; i < b.N; i++ {
         v.FieldByName("Name").SetString("张三")
         v.FieldByName("Id").SetInt(10)
         v.FieldByName("Addr").SetString("缅甸")
         v.FieldByName("Number").SetString("10086")
     }
 }

可以看出反射所带来的性能消耗是非常大的,如果使用FieldByName用字段名赋值还会多出一部分通过字段名映射字段索引的反射操作流程,导致性能比使用Field还要劣化17%左右。
在这里插入图片描述

考虑优化

这里可以优化一下,自己维护一个字典

 func BenchmarkSetStudentValueByFieldNameCache(b *testing.B) {
     typ := reflect.TypeOf(Student{})
     v := reflect.New(typ).Elem()
     cache := make(map[string]int, typ.NumField())
     for i := 0; i < typ.NumField(); i++ {
         cache[typ.Field(i).Name] = i
     }
     b.ResetTimer()
     
     for i := 0; i < b.N; i++ {
         v.Field(cache["Name"]).SetString("张三")
         v.Field(cache["Id"]).SetInt(10)
         v.Field(cache["Addr"]).SetString("缅甸")
         v.Field(cache["Number"]).SetString("10086")
     }
 }

可以看到,快了不少,比直接使用FieldByName,优化了5倍多。
在这里插入图片描述

让人眼前一亮的unsafe

顺便提一下先前说的unsafe包,在这个场景下也有用武之地

 func BenchmarkSetStudentValueByUnsafe(b *testing.B) {
     student := new(Student)
     pStudent := unsafe.Pointer(student)
     b.ResetTimer()
     for i := 0; i < b.N; i++ {
         pNameStudent := (*string)(unsafe.Pointer(uintptr(pStudent) + unsafe.Offsetof(student.Name)))
         *pNameStudent = "张三"
         pIdStudent := (*int)(unsafe.Pointer(uintptr(pStudent) + unsafe.Offsetof(student.Id)))
         *pIdStudent = 10
         pAddrStudent := (*string)(unsafe.Pointer(uintptr(pStudent) + unsafe.Offsetof(student.Addr)))
         *pAddrStudent = "缅甸"
         pNumberStudent := (*string)(unsafe.Pointer(uintptr(pStudent) + unsafe.Offsetof(student.Number)))
         *pNumberStudent = "10086"
     }
 }

可以看到,它的性能表现和直接赋值不相上下,让人眼前一亮
在这里插入图片描述

总结

反射在一些场景下非常方便,使代码看起来更加简洁精炼,易维护,但同时它带来的性能损耗也是昂贵的。因此能不用反射尽量不用反射,如果一定要用反射,考虑是否可以做一些优化。

文章来源:https://blog.csdn.net/orlobl/article/details/134967656
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。