Skip to content

Commit 016ce3b

Browse files
committed
optimize reflect cache lookups, optimize kind checks
1 parent 5443b57 commit 016ce3b

File tree

5 files changed

+158
-65
lines changed

5 files changed

+158
-65
lines changed

value/listreflect.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,15 @@ func (r listReflect) Range() ListRange {
5858
rr := lrrPool.Get().(*listReflectRange)
5959
rr.list = r.Value
6060
rr.i = -1
61+
rr.entry = nil
6162
return rr
6263
}
6364

6465
type listReflectRange struct {
65-
list reflect.Value
66-
vr *valueReflect
67-
i int
66+
list reflect.Value
67+
vr *valueReflect
68+
i int
69+
entry *TypeReflectCacheEntry
6870
}
6971

7072
func (r *listReflectRange) Next() bool {
@@ -79,7 +81,11 @@ func (r *listReflectRange) Item() (index int, value Value) {
7981
if r.i >= r.list.Len() {
8082
panic("Item() called on ListRange with no more items")
8183
}
82-
return r.i, r.vr.mustReuse(r.list.Index(r.i), nil, nil)
84+
v := r.list.Index(r.i)
85+
if r.entry == nil {
86+
r.entry = TypeReflectEntryOf(v.Type())
87+
}
88+
return r.i, r.vr.mustReuse(v, r.entry, nil, nil)
8389
}
8490

8591
func (r *listReflectRange) Recycle() {

value/mapreflect.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,21 +68,29 @@ func (r mapReflect) toMapKey(key string) reflect.Value {
6868
}
6969

7070
func (r mapReflect) Iterate(fn func(string, Value) bool) bool {
71+
if r.Value.Len() == 0 {
72+
return true
73+
}
7174
vr := reflectPool.Get().(*valueReflect)
7275
defer vr.Recycle()
73-
return eachMapEntry(r.Value, func(key reflect.Value, value reflect.Value) bool {
74-
return fn(key.String(), vr.mustReuse(value, &r.Value, &key))
76+
return eachMapEntry(r.Value, func(e *TypeReflectCacheEntry, key reflect.Value, value reflect.Value) bool {
77+
// TODO: Track cache entry
78+
return fn(key.String(), vr.mustReuse(value, nil, &r.Value, &key))
7579
})
7680
}
7781

78-
func eachMapEntry(val reflect.Value, fn func(reflect.Value, reflect.Value) bool) bool {
82+
func eachMapEntry(val reflect.Value, fn func(*TypeReflectCacheEntry, reflect.Value, reflect.Value) bool) bool {
7983
iter := val.MapRange()
84+
var entry *TypeReflectCacheEntry
8085
for iter.Next() {
8186
next := iter.Value()
8287
if !next.IsValid() {
8388
continue
8489
}
85-
if !fn(iter.Key(), next) {
90+
if entry == nil {
91+
entry = TypeReflectEntryOf(next.Type())
92+
}
93+
if !fn(entry, iter.Key(), next) {
8694
return false
8795
}
8896
}
@@ -114,6 +122,7 @@ func (r mapReflect) Equals(m Map) bool {
114122
if !ok {
115123
return false
116124
}
117-
return Equals(vr.mustReuse(lhsVal, nil, nil), value)
125+
// TODO: Track cache entry
126+
return Equals(vr.mustReuse(lhsVal, nil, nil, nil), value)
118127
})
119128
}

value/reflectcache.go

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ type FieldCacheEntry struct {
5757
// a field in a reflect.Value struct. The field indices in the list form a path used
5858
// to traverse through intermediary 'inline' fields.
5959
fieldPath [][]int
60+
61+
fieldType reflect.Type
62+
TypeEntry *TypeReflectCacheEntry
6063
}
6164

6265
// GetFrom returns the field identified by this FieldCacheEntry from the provided struct.
@@ -74,25 +77,51 @@ var unstructuredConvertableType = reflect.TypeOf(new(UnstructuredConverter)).Ele
7477
var defaultReflectCache = newReflectCache()
7578

7679
// TypeReflectEntryOf returns the TypeReflectCacheEntry of the provided reflect.Type.
77-
func TypeReflectEntryOf(t reflect.Type) TypeReflectCacheEntry {
78-
if record, ok := defaultReflectCache.get(t); ok {
80+
func TypeReflectEntryOf(t reflect.Type) *TypeReflectCacheEntry {
81+
cm := defaultReflectCache.get()
82+
if record, ok := cm[t]; ok {
83+
return record
84+
}
85+
updates := reflectCacheMap{}
86+
result := typeReflectEntryOf(cm, t, updates)
87+
if len(updates) > 0 {
88+
defaultReflectCache.update(updates)
89+
}
90+
return result
91+
}
92+
93+
// TypeReflectEntryOf returns all updates needed to add provided reflect.Type, and the types its fields transitively
94+
// depend on, to the cache.
95+
func typeReflectEntryOf(cm reflectCacheMap, t reflect.Type, updates reflectCacheMap) *TypeReflectCacheEntry {
96+
if record, ok := cm[t]; ok {
97+
return record
98+
}
99+
if record, ok := updates[t]; ok {
79100
return record
80101
}
81-
record := TypeReflectCacheEntry{
102+
typeEntry := &TypeReflectCacheEntry{
82103
isJsonMarshaler: t.Implements(marshalerType),
83104
ptrIsJsonMarshaler: reflect.PtrTo(t).Implements(marshalerType),
84105
isJsonUnmarshaler: reflect.PtrTo(t).Implements(unmarshalerType),
85106
isStringConvertable: t.Implements(unstructuredConvertableType),
86107
ptrIsStringConvertable: reflect.PtrTo(t).Implements(unstructuredConvertableType),
87108
}
88109
if t.Kind() == reflect.Struct {
89-
hints := map[string]*FieldCacheEntry{}
90-
buildStructCacheEntry(t, hints, nil)
91-
record.structFields = hints
110+
fieldEntries := map[string]*FieldCacheEntry{}
111+
buildStructCacheEntry(t, fieldEntries, nil)
112+
typeEntry.structFields = fieldEntries
92113
}
93114

94-
defaultReflectCache.update(t, record)
95-
return record
115+
// cyclic type references are allowed, so we must add the typeEntry to the updates map before resolving
116+
// the field.typeEntry references, or creating them if they are not already in the cache
117+
updates[t] = typeEntry
118+
119+
for _, field := range typeEntry.structFields {
120+
if field.TypeEntry == nil {
121+
field.TypeEntry = typeReflectEntryOf(cm, field.fieldType, updates)
122+
}
123+
}
124+
return typeEntry
96125
}
97126

98127
func buildStructCacheEntry(t reflect.Type, infos map[string]*FieldCacheEntry, fieldPath [][]int) {
@@ -106,7 +135,7 @@ func buildStructCacheEntry(t reflect.Type, infos map[string]*FieldCacheEntry, fi
106135
buildStructCacheEntry(field.Type, infos, append(fieldPath, field.Index))
107136
continue
108137
}
109-
info := &FieldCacheEntry{isOmitEmpty: isOmitempty, fieldPath: append(fieldPath, field.Index)}
138+
info := &FieldCacheEntry{isOmitEmpty: isOmitempty, fieldPath: append(fieldPath, field.Index), fieldType: t}
110139
infos[jsonName] = info
111140
}
112141
}
@@ -275,22 +304,29 @@ func newReflectCache() *typeReflectCache {
275304
return cache
276305
}
277306

278-
type reflectCacheMap map[reflect.Type]TypeReflectCacheEntry
307+
type reflectCacheMap map[reflect.Type]*TypeReflectCacheEntry
279308

280-
// get returns true and TypeReflectCacheEntry for the given type if the type is in the cache. Otherwise get returns false.
281-
func (c *typeReflectCache) get(t reflect.Type) (TypeReflectCacheEntry, bool) {
282-
entry, ok := c.value.Load().(reflectCacheMap)[t]
283-
return entry, ok
309+
// get returns the reflectCacheMap.
310+
func (c *typeReflectCache) get() reflectCacheMap {
311+
return c.value.Load().(reflectCacheMap)
284312
}
285313

286-
// update sets the TypeReflectCacheEntry for the given type via a copy-on-write update to the struct cache.
287-
func (c *typeReflectCache) update(t reflect.Type, m TypeReflectCacheEntry) {
314+
// update merges the provided updates into the cache.
315+
func (c *typeReflectCache) update(updates reflectCacheMap) {
288316
c.mu.Lock()
289317
defer c.mu.Unlock()
290318

291319
currentCacheMap := c.value.Load().(reflectCacheMap)
292-
if _, ok := currentCacheMap[t]; ok {
293-
// Bail if the entry has been set while waiting for lock acquisition.
320+
321+
hasNewEntries := false
322+
for t := range updates {
323+
if _, ok := currentCacheMap[t]; !ok {
324+
hasNewEntries = true
325+
break
326+
}
327+
}
328+
if !hasNewEntries {
329+
// Bail if the updates have been set while waiting for lock acquisition.
294330
// This is safe since setting entries is idempotent.
295331
return
296332
}
@@ -299,6 +335,8 @@ func (c *typeReflectCache) update(t reflect.Type, m TypeReflectCacheEntry) {
299335
for k, v := range currentCacheMap {
300336
newCacheMap[k] = v
301337
}
302-
newCacheMap[t] = m
338+
for t, update := range updates {
339+
newCacheMap[t] = update
340+
}
303341
c.value.Store(newCacheMap)
304342
}

value/structreflect.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type structReflect struct {
2727

2828
func (r structReflect) Length() int {
2929
i := 0
30-
eachStructField(r.Value, func(s string, value reflect.Value) bool {
30+
eachStructField(r.Value, func(_ *TypeReflectCacheEntry, s string, value reflect.Value) bool {
3131
i++
3232
return true
3333
})
@@ -94,19 +94,19 @@ func (r structReflect) update(fieldEntry *FieldCacheEntry, key string, oldVal, n
9494
func (r structReflect) Iterate(fn func(string, Value) bool) bool {
9595
vr := reflectPool.Get().(*valueReflect)
9696
defer vr.Recycle()
97-
return eachStructField(r.Value, func(s string, value reflect.Value) bool {
98-
return fn(s, vr.mustReuse(value, nil, nil))
97+
return eachStructField(r.Value, func(e *TypeReflectCacheEntry, s string, value reflect.Value) bool {
98+
return fn(s, vr.mustReuse(value, e, nil, nil))
9999
})
100100
}
101101

102-
func eachStructField(structVal reflect.Value, fn func(string, reflect.Value) bool) bool {
102+
func eachStructField(structVal reflect.Value, fn func(*TypeReflectCacheEntry, string, reflect.Value) bool) bool {
103103
for jsonName, fieldCacheEntry := range TypeReflectEntryOf(structVal.Type()).Fields() {
104104
fieldVal := fieldCacheEntry.GetFrom(structVal)
105105
if fieldCacheEntry.isOmitEmpty && (safeIsNil(fieldVal) || isZero(fieldVal)) {
106106
// omit it
107107
continue
108108
}
109-
ok := fn(jsonName, fieldVal)
109+
ok := fn(fieldCacheEntry.TypeEntry, jsonName, fieldVal)
110110
if !ok {
111111
return false
112112
}
@@ -146,7 +146,7 @@ func (r structReflect) Equals(m Map) bool {
146146
return false
147147
}
148148
lhsVal := fieldCacheEntry.GetFrom(r.Value)
149-
return Equals(vr.mustReuse(lhsVal, nil, nil), value)
149+
return Equals(vr.mustReuse(lhsVal, nil, nil, nil), value)
150150
})
151151
}
152152

0 commit comments

Comments
 (0)