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

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

总结

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

相关推荐
Gofarlic_oms12 小时前
利用API实现ANSYS许可证管理自动化集成
运维·服务器·开发语言·matlab·自动化·负载均衡
AI+程序员在路上3 小时前
VS Code 完全使用指南:下载、安装、核心功能与 内置AI 编程助手实战
开发语言·人工智能·windows·开源
invicinble3 小时前
这里对java的知识体系做一个全域的介绍
java·开发语言·python
小码哥_常3 小时前
MyBatis-Plus:让数据库操作飞起来的神器
后端
catchadmin3 小时前
使用 PHP TrueAsync 改造 Laravel 协程异步化的可行路径
开发语言·php·laravel
wbs_scy3 小时前
【Linux 线程进阶】进程 vs 线程资源划分 + 线程控制全详解
java·开发语言
2301_811274314 小时前
基于SpringBoot的智能家居管理系统
spring boot·后端·智能家居
AI人工智能+电脑小能手4 小时前
【大白话说Java面试题】【Java基础篇】第15题:JDK1.7中HashMap扩容为什么会发生死循环?如何解决
java·开发语言·数据结构·后端·面试·哈希算法
舒一笑4 小时前
我把设备指纹生成逻辑拆开了:它到底凭什么区分不同设备?
后端·程序员·掘金技术征文
Nicander5 小时前
多数据源下@transcation事务踩坑
java·后端