Skip to content

Commit b8515d0

Browse files
authored
Merge pull request #153 from jpbetz/compare-optimizations
Optimize TypedValue.Compare for value reflector
2 parents 9a93042 + 719dc37 commit b8515d0

17 files changed

+580
-171
lines changed

fieldpath/fromvalue.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ func (w *objectWalker) walk() {
5555
case w.value.IsList():
5656
// If the list were atomic, we'd break here, but we don't have
5757
// a schema, so we can't tell.
58-
iter := w.value.AsList().Range()
58+
l := w.value.AsList()
59+
defer l.Recycle()
60+
iter := l.Range()
5961
defer iter.Recycle()
6062
for iter.Next() {
6163
i, value := iter.Item()
@@ -69,7 +71,9 @@ func (w *objectWalker) walk() {
6971
// If the map/struct were atomic, we'd break here, but we don't
7072
// have a schema, so we can't tell.
7173

72-
w.value.AsMap().Iterate(func(k string, val value.Value) bool {
74+
m := w.value.AsMap()
75+
defer m.Recycle()
76+
m.Iterate(func(k string, val value.Value) bool {
7377
w2 := *w
7478
w2.path = append(w.path, PathElement{FieldName: &k})
7579
w2.value = val
@@ -106,9 +110,11 @@ func GuessBestListPathElement(index int, item value.Value) PathElement {
106110
return PathElement{Index: &index}
107111
}
108112

113+
m := item.AsMap()
114+
defer m.Recycle()
109115
var keys value.FieldList
110116
for _, name := range AssociativeListCandidateFieldNames {
111-
f, ok := item.AsMap().Get(name)
117+
f, ok := m.Get(name)
112118
if !ok {
113119
continue
114120
}

typed/helpers.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,10 @@ func keyedAssociativeListItemToPathElement(list *schema.List, index int, child v
191191
return pe, errors.New("associative list with keys may not have non-map elements")
192192
}
193193
keyMap := value.FieldList{}
194+
m := child.AsMap()
195+
defer m.Recycle()
194196
for _, fieldName := range list.Keys {
195-
if val, ok := child.AsMap().Get(fieldName); ok {
197+
if val, ok := m.Get(fieldName); ok {
196198
keyMap = append(keyMap, value.Field{Name: fieldName, Value: val})
197199
} else {
198200
return pe, fmt.Errorf("associative list with keys has an element that omits key field %q", fieldName)

typed/merge.go

Lines changed: 29 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -148,18 +148,15 @@ func (w *mergingWalker) finishDescent(w2 *mergingWalker) {
148148
*w.spareWalkers = append(*w.spareWalkers, w2)
149149
}
150150

151-
func (w *mergingWalker) derefMap(prefix string, v value.Value, dest *value.Map) (errs ValidationErrors) {
152-
// taking dest as input so that it can be called as a one-liner with
153-
// append.
151+
func (w *mergingWalker) derefMap(prefix string, v value.Value) (value.Map, ValidationErrors) {
154152
if v == nil {
155-
return nil
153+
return nil, nil
156154
}
157155
m, err := mapValue(v)
158156
if err != nil {
159-
return errorf("%v: %v", prefix, err)
157+
return nil, errorf("%v: %v", prefix, err)
160158
}
161-
*dest = m
162-
return nil
159+
return m, nil
163160
}
164161

165162
func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs value.List) (errs ValidationErrors) {
@@ -253,24 +250,26 @@ func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs value.List) (err
253250
return errs
254251
}
255252

256-
func (w *mergingWalker) derefList(prefix string, v value.Value, dest *value.List) (errs ValidationErrors) {
257-
// taking dest as input so that it can be called as a one-liner with
258-
// append.
253+
func (w *mergingWalker) derefList(prefix string, v value.Value) (value.List, ValidationErrors) {
259254
if v == nil {
260-
return nil
255+
return nil, nil
261256
}
262257
l, err := listValue(v)
263258
if err != nil {
264-
return errorf("%v: %v", prefix, err)
259+
return nil, errorf("%v: %v", prefix, err)
265260
}
266-
*dest = l
267-
return nil
261+
return l, nil
268262
}
269263

270264
func (w *mergingWalker) doList(t *schema.List) (errs ValidationErrors) {
271-
var lhs, rhs value.List
272-
w.derefList("lhs: ", w.lhs, &lhs)
273-
w.derefList("rhs: ", w.rhs, &rhs)
265+
lhs, _ := w.derefList("lhs: ", w.lhs)
266+
if lhs != nil {
267+
defer lhs.Recycle()
268+
}
269+
rhs, _ := w.derefList("rhs: ", w.rhs)
270+
if rhs != nil {
271+
defer rhs.Recycle()
272+
}
274273

275274
// If both lhs and rhs are empty/null, treat it as a
276275
// leaf: this helps preserve the empty/null
@@ -311,31 +310,10 @@ func (w *mergingWalker) visitMapItem(t *schema.Map, out map[string]interface{},
311310
func (w *mergingWalker) visitMapItems(t *schema.Map, lhs, rhs value.Map) (errs ValidationErrors) {
312311
out := map[string]interface{}{}
313312

314-
if lhs != nil {
315-
lhs.Iterate(func(key string, val value.Value) bool {
316-
var rval value.Value
317-
if rhs != nil {
318-
if item, ok := rhs.Get(key); ok {
319-
rval = item
320-
defer rval.Recycle()
321-
}
322-
}
323-
errs = append(errs, w.visitMapItem(t, out, key, val, rval)...)
324-
return true
325-
})
326-
}
327-
328-
if rhs != nil {
329-
rhs.Iterate(func(key string, val value.Value) bool {
330-
if lhs != nil {
331-
if lhs.Has(key) {
332-
return true
333-
}
334-
}
335-
errs = append(errs, w.visitMapItem(t, out, key, nil, val)...)
336-
return true
337-
})
338-
}
313+
value.MapZip(lhs, rhs, value.Unordered, func(key string, lhsValue, rhsValue value.Value) bool {
314+
errs = append(errs, w.visitMapItem(t, out, key, lhsValue, rhsValue)...)
315+
return true
316+
})
339317
if len(out) > 0 {
340318
i := interface{}(out)
341319
w.out = &i
@@ -345,14 +323,18 @@ func (w *mergingWalker) visitMapItems(t *schema.Map, lhs, rhs value.Map) (errs V
345323
}
346324

347325
func (w *mergingWalker) doMap(t *schema.Map) (errs ValidationErrors) {
348-
var lhs, rhs value.Map
349-
w.derefMap("lhs: ", w.lhs, &lhs)
350-
w.derefMap("rhs: ", w.rhs, &rhs)
351-
326+
lhs, _ := w.derefMap("lhs: ", w.lhs)
327+
if lhs != nil {
328+
defer lhs.Recycle()
329+
}
330+
rhs, _ := w.derefMap("rhs: ", w.rhs)
331+
if rhs != nil {
332+
defer rhs.Recycle()
333+
}
352334
// If both lhs and rhs are empty/null, treat it as a
353335
// leaf: this helps preserve the empty/null
354336
// distinction.
355-
emptyPromoteToLeaf := (lhs == nil || lhs.Length() == 0) && (rhs == nil || rhs.Length() == 0)
337+
emptyPromoteToLeaf := (lhs == nil || lhs.Empty()) && (rhs == nil || rhs.Empty())
356338

357339
if t.ElementRelationship == schema.Atomic || emptyPromoteToLeaf {
358340
w.doLeaf()

typed/remove.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func (w *removingWalker) doScalar(t *schema.Scalar) ValidationErrors {
4343

4444
func (w *removingWalker) doList(t *schema.List) (errs ValidationErrors) {
4545
l := w.value.AsList()
46-
46+
defer l.Recycle()
4747
// If list is null, empty, or atomic just return
4848
if l == nil || l.Length() == 0 || t.ElementRelationship == schema.Atomic {
4949
return nil
@@ -73,9 +73,11 @@ func (w *removingWalker) doList(t *schema.List) (errs ValidationErrors) {
7373

7474
func (w *removingWalker) doMap(t *schema.Map) ValidationErrors {
7575
m := w.value.AsMap()
76-
76+
if m != nil {
77+
defer m.Recycle()
78+
}
7779
// If map is null, empty, or atomic just return
78-
if m == nil || m.Length() == 0 || t.ElementRelationship == schema.Atomic {
80+
if m == nil || m.Empty() || t.ElementRelationship == schema.Atomic {
7981
return nil
8082
}
8183

typed/tofieldset.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,9 @@ func (v *toFieldSetWalker) visitMapItems(t *schema.Map, m value.Map) (errs Valid
144144

145145
func (v *toFieldSetWalker) doMap(t *schema.Map) (errs ValidationErrors) {
146146
m, _ := mapValue(v.value)
147-
147+
if m != nil {
148+
defer m.Recycle()
149+
}
148150
if t.ElementRelationship == schema.Atomic {
149151
v.set.Insert(v.path)
150152
return nil

typed/validate.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,10 @@ func (v *validatingObjectWalker) doMap(t *schema.Map) (errs ValidationErrors) {
179179
if err != nil {
180180
return errorf(err.Error())
181181
}
182-
183182
if m == nil {
184183
return nil
185184
}
186-
185+
defer m.Recycle()
187186
errs = v.visitMapItems(t, m)
188187

189188
return errs

value/list.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ type List interface {
2525
At(int) Value
2626
// Range returns a ListRange for iterating over the items in the list.
2727
Range() ListRange
28+
29+
// Equals compares the two list, and return true if they are the same, false otherwise.
30+
// Implementations can use ListEquals as a general implementation for this methods.
31+
Equals(List) bool
32+
// Recycle gives back this List once it is no longer needed. The
33+
// value shouldn't be used after this call.
34+
Recycle()
2835
}
2936

3037
// ListRange represents a single iteration across the items of a list.
@@ -36,7 +43,7 @@ type ListRange interface {
3643
// pointers to the value returned by Item() that escape the iteration loop since they become invalid once either
3744
// Item() or Recycle() is called.
3845
Item() (index int, value Value)
39-
// Recycle returns a ListRange that is no longer needed. The value returned by Item() becomes invalid once this is
46+
// Recycle gives back this ListRange once it is no longer needed. The value returned by Item() becomes invalid once this is
4047
// called.
4148
Recycle()
4249
}
@@ -56,6 +63,7 @@ func (_ *emptyRange) Item() (index int, value Value) {
5663
func (_ *emptyRange) Recycle() {}
5764

5865
// ListEquals compares two lists lexically.
66+
// WARN: This is a naive implementation, calling lhs.Equals(rhs) is typically the most efficient.
5967
func ListEquals(lhs, rhs List) bool {
6068
if lhs.Length() != rhs.Length() {
6169
return false

value/listreflect.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ var lrrPool = sync.Pool{
5050
},
5151
}
5252

53+
func (r *listReflect) Recycle() {
54+
listReflectPool.Put(r)
55+
}
56+
5357
func (r listReflect) Range() ListRange {
5458
length := r.Value.Len()
5559
if length == 0 {
@@ -58,10 +62,17 @@ func (r listReflect) Range() ListRange {
5862
rr := lrrPool.Get().(*listReflectRange)
5963
rr.list = r.Value
6064
rr.i = -1
61-
rr.entry = nil
65+
rr.entry = TypeReflectEntryOf(r.Value.Type().Elem())
6266
return rr
6367
}
6468

69+
func (r listReflect) Equals(other List) bool {
70+
if otherReflectList, ok := other.(*listReflect); ok {
71+
return reflect.DeepEqual(r.Value.Interface(), otherReflectList.Value.Interface())
72+
}
73+
return ListEquals(&r, other)
74+
}
75+
6576
type listReflectRange struct {
6677
list reflect.Value
6778
vr *valueReflect
@@ -82,9 +93,6 @@ func (r *listReflectRange) Item() (index int, value Value) {
8293
panic("Item() called on ListRange with no more items")
8394
}
8495
v := r.list.Index(r.i)
85-
if r.entry == nil {
86-
r.entry = TypeReflectEntryOf(v.Type())
87-
}
8896
return r.i, r.vr.mustReuse(v, r.entry, nil, nil)
8997
}
9098

value/listunstructured.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,20 @@ func (l listUnstructured) At(i int) Value {
2828
return NewValueInterface(l[i])
2929
}
3030

31+
func (l listUnstructured) Equals(other List) bool {
32+
return ListEquals(&l, other)
33+
}
34+
3135
var lurPool = sync.Pool{
3236
New: func() interface{} {
3337
return &listUnstructuredRange{vv: &valueUnstructured{}}
3438
},
3539
}
3640

41+
func (_ listUnstructured) Recycle() {
42+
43+
}
44+
3745
func (l listUnstructured) Range() ListRange {
3846
if len(l) == 0 {
3947
return EmptyRange

0 commit comments

Comments
 (0)