缓存 reflect.Type 是最值得做的第一步,因其每次调用均分配新对象,而同一类型指针恒定,可用 map[reflect.Type]struct{} 或 map[uintptr]T 安全复用。

缓存 reflect.Type 是最值得做的第一步
每次调用 reflect.TypeOf 都会生成新对象,虽然底层类型元数据不重复计算,但分配开销和 GC 压力在高频路径中不可忽视。同一类型的 reflect.TypeOf 返回值指针恒定,完全可作为 map key 直接复用。
- 正确做法:用
map[reflect.Type]struct{}或map[uintptr]T(uintptr(unsafe.Pointer(t)))做缓存键,避免用map[string]T(t.String()触发字符串分配)或map[interface{}]T(接口底层结构不同导致命中失败) - 初始化阶段预热即可,无需
sync.Map——reflect.Type是只读且并发安全的 - 常见误判:以为缓存
reflect.ValueOf有用,其实它每次新建实例,无法复用,也不该当 key
字段访问别再用 FieldByName,改用预计算的 FieldByIndex
FieldByName 是线性遍历 + 字符串比对,100 个字段就要比 100 次;而 FieldByIndex 是 O(1) 查表,前提是索引提前算好并缓存。
- 启动时或首次访问时,对目标结构体调用
t := reflect.TypeOf(T{}),再用t.FieldByName("Name")获取StructField,提取.Index存入map[string][]int - 后续直接
v.FieldByIndex(cachedIndex),跳过全部字符串操作 - 注意:嵌套结构体需用多层索引(如
[]int{0, 1, 2}),不能只存单个int
MethodByName 的缓存必须配 Method 而非 Value
缓存 reflect.Value.MethodByName("Save") 只省掉 15–20% 开销,真正耗时的是 Call 本身。应缓存 reflect.Method 或其封装后的 reflect.Value 实例。
- 推荐方式:用
t.Method(i)遍历一次方法表,按名称建map[string]reflect.Method,再用reflect.ValueOf(recv).Method(int)得到可调用的reflect.Value - 更进一步:用
reflect.MakeFunc在初始化阶段生成闭包,后续调用就是普通函数,彻底绕过Call开销(仅适用于签名固定、接收者明确的场景) - 别缓存
reflect.Value本身——它不可比较、无法当 map key,且每次调用都新建
unsafe.Offsetof + unsafe.Pointer 是零反射访问的终极手段
如果结构体字段顺序稳定、无 //go:notinheap 标记,且能接受字段重排即失效的风险,直接内存偏移访问比任何反射都快。
- 初始化时用
unsafe.Offsetof(T{}.FieldName)或t.FieldByName("FieldName").Offset算出偏移量,存为全局变量 - 访问时:
*(*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + nameOffset)) - 必须加
//go:build !race条件编译,否则 race detector 会报错;字段对齐变化(如加新字段到中间)会导致 offset 失效,这是“语言学习”的硬约束


















