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

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

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

案例

字段赋值方式对比

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倍左右。

考虑优化

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

go 复制代码
 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包,在这个场景下也有用武之地

go 复制代码
 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"
     }
 }

可以看到,它的性能表现和直接赋值不相上下,让人眼前一亮

总结

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

相关推荐
monkey_meng2 分钟前
【Rust中的项目管理】
开发语言·rust·源代码管理
喜欢打篮球的普通人4 分钟前
rust高级特征
开发语言·后端·rust
ModelBulider22 分钟前
十三、注解配置SpringMVC
java·开发语言·数据库·sql·mysql
V搜xhliang024631 分钟前
基于深度学习的地物类型的提取
开发语言·人工智能·python·深度学习·神经网络·学习·conda
DK七七32 分钟前
多端校园圈子论坛小程序,多个学校同时代理,校园小程序分展示后台管理源码
开发语言·前端·微信小程序·小程序·php
苹果酱056736 分钟前
C语言 char 字符串 - C语言零基础入门教程
java·开发语言·spring boot·mysql·中间件
代码小鑫1 小时前
A032-基于Spring Boot的健康医院门诊在线挂号系统
java·开发语言·spring boot·后端·spring·毕业设计
训山1 小时前
4000字浅谈Java网络编程
java·开发语言·网络
API快乐传递者1 小时前
除了网页标题,还能用爬虫抓取哪些信息?
开发语言·爬虫·python
豌豆花下猫1 小时前
REST API 已经 25 岁了:它是如何形成的,将来可能会怎样?
后端·python·ai