Minimal shared cache for struct field paths
This commit is contained in:
+58
-27
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user