Minimal shared cache for struct field paths

This commit is contained in:
Thomas Pelletier
2021-03-25 20:19:58 -04:00
parent 17299c937b
commit 1da2fc7e28
+58 -27
View File
@@ -5,6 +5,7 @@ import (
"math" "math"
"reflect" "reflect"
"strings" "strings"
"sync"
) )
type target interface { type target interface {
@@ -466,41 +467,71 @@ func scopeMap(v reflect.Value, name string) (target, bool, error) {
}, true, nil }, true, nil
} }
type fieldPathsMap = map[string][]int
type fieldPathsCache struct {
m map[reflect.Type]fieldPathsMap
l sync.RWMutex
}
func (c *fieldPathsCache) get(t reflect.Type) (fieldPathsMap, bool) {
c.l.RLock()
paths, ok := c.m[t]
c.l.RUnlock()
return paths, ok
}
func (c *fieldPathsCache) set(t reflect.Type, m fieldPathsMap) {
c.l.Lock()
c.m[t] = m
c.l.Unlock()
}
var globalFieldPathsCache = fieldPathsCache{
m: map[reflect.Type]fieldPathsMap{},
l: sync.RWMutex{},
}
func scopeStruct(v reflect.Value, name string) (target, bool, error) { func scopeStruct(v reflect.Value, name string) (target, bool, error) {
// TODO: cache this, and reduce allocations // TODO: cache this, and reduce allocations
fieldPaths := map[string][]int{} fieldPaths, ok := globalFieldPathsCache.get(v.Type())
if !ok {
fieldPaths = map[string][]int{}
path := make([]int, 0, 16) path := make([]int, 0, 16)
var walk func(reflect.Value) var walk func(reflect.Value)
walk = func(v reflect.Value) { walk = func(v reflect.Value) {
t := v.Type() t := v.Type()
for i := 0; i < t.NumField(); i++ { for i := 0; i < t.NumField(); i++ {
l := len(path) l := len(path)
path = append(path, i) path = append(path, i)
f := t.Field(i) f := t.Field(i)
if f.PkgPath != "" { if f.PkgPath != "" {
// only consider exported fields // only consider exported fields
} else if f.Anonymous { } else if f.Anonymous {
walk(v.Field(i)) walk(v.Field(i))
} else { } else {
fieldName, ok := f.Tag.Lookup("toml") fieldName, ok := f.Tag.Lookup("toml")
if !ok { if !ok {
fieldName = f.Name fieldName = f.Name
}
pathCopy := make([]int, len(path))
copy(pathCopy, path)
fieldPaths[fieldName] = pathCopy
// extra copy for the case-insensitive match
fieldPaths[strings.ToLower(fieldName)] = pathCopy
} }
path = path[:l]
pathCopy := make([]int, len(path))
copy(pathCopy, path)
fieldPaths[fieldName] = pathCopy
// extra copy for the case-insensitive match
fieldPaths[strings.ToLower(fieldName)] = pathCopy
} }
path = path[:l]
} }
}
walk(v) walk(v)
globalFieldPathsCache.set(v.Type(), fieldPaths)
}
path, ok := fieldPaths[name] path, ok := fieldPaths[name]
if !ok { if !ok {