package resx import ( "reflect" "sync" ) // 本文件为 GetStruct/GetSliceStruct 提供结果类型缓存。 // // 动机: reflect.StructOf 即使命中 runtime 自己的类型缓存, 仍然要重新构造 // []StructField、算签名、加锁查表。实测 12 字段的 struct 约 // // reflect.StructOf(相同字段, 已缓存) 2349 ns / 2549 B / 31 allocs // reflect.TypeOf(已知类型) 2.3 ns / 0 B / 0 allocs // // 相差三个数量级。而资源的 fields 在构造之后不再变化, 相同的 extends 必然 // 得到同一个类型, 所以完全可以缓存下来复用。 // resStructCache 按 extends 缓存 GetStruct 的结果类型。 // // 用切片线性扫描而不是 map: 键是 []reflect.StructField, 要做 map key 得先拼 // 字符串, 那笔开销反而可能盖过 StructOf 本身。而 extends 的取值组合来自关联 // 配置, 数量很少(通常个位数), 逐项比对最省 —— reflect.StructField 的 // Name/Type/Tag 三者都可比较。 type resStructCache struct { mu sync.RWMutex base reflect.Type // extends 为空时的类型(最常见的调用形态) entries []resStructCacheEntry } type resStructCacheEntry struct { extends []reflect.StructField typ reflect.Type } // resStructCacheLimit 限制缓存条目数。正常配置下远远用不满; 万一调用方拿运行时 // 生成的字段做 extends, 超过上限后退回每次现算, 保证内存有界。 const resStructCacheLimit = 64 // sameExtends 判断两组 extends 是否等价。 func sameExtends(a, b []reflect.StructField) bool { if len(a) != len(b) { return false } for i := range a { if a[i].Name != b[i].Name || a[i].Type != b[i].Type || a[i].Tag != b[i].Tag { return false } } return true } // load 查缓存, 未命中返回 nil。 func (c *resStructCache) load(extends []reflect.StructField) reflect.Type { c.mu.RLock() defer c.mu.RUnlock() if len(extends) == 0 { return c.base } for i := range c.entries { if sameExtends(c.entries[i].extends, extends) { return c.entries[i].typ } } return nil } // store 写入缓存。 // // extends 会复制一份再存: 调用方常把它当临时缓冲区复用(append 到同一个底层 // 数组上), 直接持有会让缓存键在之后被改写, 变成命中错误类型的隐蔽 bug。 func (c *resStructCache) store(extends []reflect.StructField, t reflect.Type) { c.mu.Lock() defer c.mu.Unlock() if len(extends) == 0 { c.base = t return } if len(c.entries) >= resStructCacheLimit { return } key := make([]reflect.StructField, len(extends)) copy(key, extends) c.entries = append(c.entries, resStructCacheEntry{extends: key, typ: t}) }