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"
     }
 }

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

总结

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

相关推荐
hdsoft_huge6 小时前
Java & Spring Boot常见异常全解析:原因、危害、处理与防范
java·开发语言·spring boot
风中的微尘6 小时前
39.网络流入门
开发语言·网络·c++·算法
文人sec6 小时前
性能测试-jmeter9-逻辑控制器、定时器压力并发
测试工具·jmeter·性能优化·模块测试
未来之窗软件服务7 小时前
幽冥大陆(二)RDIFSDK 接口文档:布草洗涤厂高效运营的技术桥梁C#—东方仙盟
开发语言·c#·rdif·仙盟创梦ide·东方仙盟
小冯记录编程7 小时前
C++指针陷阱:高效背后的致命危险
开发语言·c++·visual studio
1uther7 小时前
Unity核心概念⑨:Screen
开发语言·游戏·unity·c#·游戏引擎
C_Liu_8 小时前
C++:类和对象(下)
开发语言·c++
coderxiaohan8 小时前
【C++】类和对象1
java·开发语言·c++
用户21411832636028 小时前
Qwen3-Coder 实战!历史人物短视频一键生成,多分镜人物不崩,魔搭直接玩
后端
追逐时光者8 小时前
C#/.NET/.NET Core技术前沿周刊 | 第 54 期(2025年9.8-9.14)
后端·.net