perf: 缓存 GetStruct 构造的 struct 类型
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 必然得到 同一个类型, 完全可以缓存复用。 GetStruct/GetSliceStruct 抽出共用的 structType(), 结果按 extends 缓存: GetStruct() 1399 ns / 2352 B / 28 allocs -> 22.2 ns / 48 B / 1 alloc 63x GetStruct(extends) 1673 ns / 3592 B / 31 allocs -> 47.1 ns / 176 B / 2 allocs 36x GetSliceStruct() 1457 ns / 2376 B / 29 allocs -> 31.5 ns / 24 B / 1 alloc 46x 缓存用切片线性扫描而不是 map: 键是 []reflect.StructField, 要做 map key 得先 拼字符串, 那笔开销反而可能盖过 StructOf 本身; 而 extends 的取值组合来自关联 配置, 数量很少, 逐项比对最省。条目数设了上限, 超出后退回每次现算, 保证内存有界。 一个容易踩的坑: 缓存键会复制一份再存。调用方常把 extends 当临时缓冲区复用 (append 到同一个底层数组上), 直接持有会让键在之后被改写, 变成命中错误类型的 隐蔽 bug。TestGetStructExtendsAliasing 专门守这个。 行为不变: GetStruct 仍然每次返回新实例, 只是复用类型。 新增 4 个测试(类型稳定性/extends 隔离/别名安全/并发)与 3 个基准。
This commit is contained in:
+22
-9
@@ -102,6 +102,9 @@ type resource struct {
|
||||
primarykey string
|
||||
historyRoles []string
|
||||
fields []req.ResField
|
||||
|
||||
// structCache 缓存 GetStruct 构造出来的类型,见 res_struct_cache.go
|
||||
structCache resStructCache
|
||||
}
|
||||
|
||||
// intercept 从容器里解析 DataInterceptor 并调用;容器里没有注册时返回 nil, nil, nil(不过滤、不抓取)。
|
||||
@@ -337,8 +340,24 @@ func (this *resource) GetDBTable(u req.User, opts ...req.ResOption) (sd *db.Sele
|
||||
}
|
||||
|
||||
func (this *resource) GetStruct(extends ...reflect.StructField) any {
|
||||
fields := []reflect.StructField{}
|
||||
return reflect.New(this.structType(extends)).Interface()
|
||||
}
|
||||
|
||||
func (this *resource) GetSliceStruct(extends ...reflect.StructField) any {
|
||||
return reflect.New(reflect.SliceOf(this.structType(extends))).Interface()
|
||||
}
|
||||
|
||||
// structType 返回资源字段(可选叠加 extends)对应的 struct 类型。
|
||||
//
|
||||
// 结果按 extends 缓存: fields 在资源构造之后不再变化, 相同的 extends 必然得到
|
||||
// 同一个类型, 没必要每次都走一遍 reflect.StructOf —— 它即使命中 runtime 的类型
|
||||
// 缓存也要 2μs 上下并产生几十次分配, 详见 res_struct_cache.go。
|
||||
func (this *resource) structType(extends []reflect.StructField) reflect.Type {
|
||||
if t := this.structCache.load(extends); t != nil {
|
||||
return t
|
||||
}
|
||||
|
||||
fields := make([]reflect.StructField, 0, len(this.fields)+len(extends))
|
||||
for _, field := range this.fields {
|
||||
code := field.GetCode()
|
||||
if unicode.IsLetter(rune(code[0])) {
|
||||
@@ -351,14 +370,8 @@ func (this *resource) GetStruct(extends ...reflect.StructField) any {
|
||||
})
|
||||
|
||||
t := reflect.StructOf(fields)
|
||||
|
||||
return reflect.New(t).Interface()
|
||||
}
|
||||
|
||||
func (this *resource) GetSliceStruct(extends ...reflect.StructField) any {
|
||||
t := reflect.TypeOf(this.GetStruct(extends...))
|
||||
st := reflect.SliceOf(t.Elem())
|
||||
return reflect.New(st).Interface()
|
||||
this.structCache.store(extends, t)
|
||||
return t
|
||||
}
|
||||
|
||||
// isLocalDB 判断是否为本地文件型数据库(LastInsertId 返回最后一条而非第一条)
|
||||
|
||||
Reference in New Issue
Block a user