Skip to content

Commit e9a54b4

Browse files
committed
Optimize ListEquals and Map.Equals, Add List.Range
1 parent e060266 commit e9a54b4

File tree

9 files changed

+150
-33
lines changed

9 files changed

+150
-33
lines changed

fieldpath/fromvalue.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,13 @@ 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-
list := w.value.AsList()
59-
for i := 0; i < list.Length(); i++ {
58+
iter := w.value.AsList().Range()
59+
defer iter.Recycle()
60+
for iter.Next() {
61+
i, value := iter.Item()
6062
w2 := *w
61-
w2.path = append(w.path, GuessBestListPathElement(i, list.At(i)))
62-
w2.value = list.At(i)
63+
w2.path = append(w.path, GuessBestListPathElement(i, value))
64+
w2.value = value
6365
w2.walk()
6466
}
6567
return

typed/remove.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ func (w *removingWalker) doList(t *schema.List) (errs ValidationErrors) {
5050
}
5151

5252
var newItems []interface{}
53-
for i := 0; i < l.Length(); i++ {
54-
item := l.At(i)
53+
iter := l.Range()
54+
defer iter.Recycle()
55+
for iter.Next() {
56+
i, item := iter.Item()
5557
// Ignore error because we have already validated this list
5658
pe, _ := listItemToPathElement(t, i, item)
5759
path, _ := fieldpath.MakePath(pe)

value/list.go

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,22 @@ type List interface {
2323
// At returns the item at the given position in the map. It will
2424
// panic if the index is out of range.
2525
At(int) Value
26+
// Range returns a ListRange for iterating over the items in the list.
27+
Range() ListRange
28+
}
29+
30+
// ListRange represents a single iteration across the items of a list.
31+
type ListRange interface {
32+
// Next increments to the next item in the range, if there is one, and returns true, or returns false if there are no more items.
33+
Next() bool
34+
// Item returns the index and value of the current item in the range. or panics if there is no current item.
35+
// For efficiency, Item may reuse the values returned by previous Item calls. Callers should be careful avoid holding
36+
// pointers to the value returned by Item() that escape the iteration loop since they become invalid once either
37+
// Item() or Recycle() is called.
38+
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
40+
// called.
41+
Recycle()
2642
}
2743

2844
// ListEquals compares two lists lexically.
@@ -31,16 +47,17 @@ func ListEquals(lhs, rhs List) bool {
3147
return false
3248
}
3349

34-
for i := 0; i < lhs.Length(); i++ {
35-
lv := lhs.At(i)
36-
rv := rhs.At(i)
50+
lhsRange := lhs.Range()
51+
defer lhsRange.Recycle()
52+
rhsRange := rhs.Range()
53+
defer rhsRange.Recycle()
54+
55+
for lhsRange.Next() && rhsRange.Next() {
56+
_, lv := lhsRange.Item()
57+
_, rv := rhsRange.Item()
3758
if !Equals(lv, rv) {
38-
lv.Recycle()
39-
rv.Recycle()
4059
return false
4160
}
42-
lv.Recycle()
43-
rv.Recycle()
4461
}
4562
return true
4663
}
@@ -53,28 +70,31 @@ func ListLess(lhs, rhs List) bool {
5370
// ListCompare compares two lists lexically. The result will be 0 if l==rhs, -1
5471
// if l < rhs, and +1 if l > rhs.
5572
func ListCompare(lhs, rhs List) int {
56-
i := 0
73+
lhsRange := lhs.Range()
74+
defer lhsRange.Recycle()
75+
rhsRange := rhs.Range()
76+
defer rhsRange.Recycle()
77+
5778
for {
58-
if i >= lhs.Length() && i >= rhs.Length() {
79+
lhsOk := lhsRange.Next()
80+
rhsOk := rhsRange.Next()
81+
if !lhsOk && !rhsOk {
5982
// Lists are the same length and all items are equal.
6083
return 0
6184
}
62-
if i >= lhs.Length() {
85+
if !lhsOk {
6386
// LHS is shorter.
6487
return -1
6588
}
66-
if i >= rhs.Length() {
89+
if !rhsOk {
6790
// RHS is shorter.
6891
return 1
6992
}
70-
lv := lhs.At(i)
71-
rv := rhs.At(i)
93+
_, lv := lhsRange.Item()
94+
_, rv := rhsRange.Item()
7295
if c := Compare(lv, rv); c != 0 {
7396
return c
7497
}
75-
lv.Recycle()
76-
rv.Recycle()
7798
// The items are equal; continue.
78-
i++
7999
}
80100
}

value/listreflect.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,33 @@ func (r listReflect) Unstructured() interface{} {
4040
}
4141
return result
4242
}
43+
44+
func (r listReflect) Range() ListRange {
45+
return &listReflectRange{r.Value, newTempValuePooler(), -1, r.Value.Len()}
46+
}
47+
48+
type listReflectRange struct {
49+
val reflect.Value
50+
pooler *tempValuePooler
51+
i int
52+
length int
53+
}
54+
55+
func (r *listReflectRange) Next() bool {
56+
r.i += 1
57+
return r.i < r.length
58+
}
59+
60+
func (r *listReflectRange) Item() (index int, value Value) {
61+
if r.i < 0 {
62+
panic("Item() called before first calling Next()")
63+
}
64+
if r.i >= r.length {
65+
panic("Item() called on ListRange with no more items")
66+
}
67+
return r.i, r.pooler.NewValueReflect(r.val.Index(r.i))
68+
}
69+
70+
func (r *listReflectRange) Recycle() {
71+
r.pooler.Recycle()
72+
}

value/listunstructured.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,41 @@ func (l listUnstructured) Length() int {
2525
func (l listUnstructured) At(i int) Value {
2626
return NewValueInterface(l[i])
2727
}
28+
29+
func (l listUnstructured) Range() ListRange {
30+
if len(l) == 0 {
31+
return &listUnstructuredRange{l, nil, -1, 0}
32+
}
33+
vv := viPool.Get().(*valueUnstructured)
34+
return &listUnstructuredRange{l, vv, -1, len(l)}
35+
}
36+
37+
type listUnstructuredRange struct {
38+
list listUnstructured
39+
vv *valueUnstructured
40+
i int
41+
length int
42+
}
43+
44+
func (r *listUnstructuredRange) Next() bool {
45+
r.i += 1
46+
return r.i < r.length
47+
}
48+
49+
func (r *listUnstructuredRange) Item() (index int, value Value) {
50+
if r.i < 0 {
51+
panic("Item() called before first calling Next()")
52+
}
53+
if r.i >= r.length {
54+
panic("Item() called on ListRange with no more items")
55+
}
56+
57+
r.vv.Value = r.list[r.i]
58+
return r.i, r.vv
59+
}
60+
61+
func (r *listUnstructuredRange) Recycle() {
62+
if r.vv != nil {
63+
r.vv.Recycle()
64+
}
65+
}

value/map.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ func MapCompare(lhs, rhs Map) int {
9999
// MapEquals returns true if lhs == rhs, false otherwise. This function
100100
// acts on generic types and should not be used by callers, but can help
101101
// implement Map.Equals.
102+
// WARN: This is a naive implementation, calling lhs.Equals(rhs) is typically far more efficient.
102103
func MapEquals(lhs, rhs Map) bool {
103104
if lhs.Length() != rhs.Length() {
104105
return false

value/mapreflect.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,17 @@ func (r mapReflect) Length() int {
3030
}
3131

3232
func (r mapReflect) Get(key string) (Value, bool) {
33-
mapKey := r.toMapKey(key)
34-
val := r.Value.MapIndex(mapKey)
35-
if !val.IsValid() {
33+
k, v, ok := r.get(key)
34+
if !ok {
3635
return nil, false
3736
}
38-
return mustWrapValueReflectMapItem(&r.Value, &mapKey, val), val != reflect.Value{}
37+
return mustWrapValueReflectMapItem(&r.Value, &k, v), true
38+
}
39+
40+
func (r mapReflect) get(k string) (key, value reflect.Value, ok bool) {
41+
mapKey := r.toMapKey(k)
42+
val := r.Value.MapIndex(mapKey)
43+
return mapKey, val, val.IsValid() && val != reflect.Value{}
3944
}
4045

4146
func (r mapReflect) Has(key string) bool {
@@ -94,16 +99,21 @@ func (r mapReflect) Unstructured() interface{} {
9499
}
95100

96101
func (r mapReflect) Equals(m Map) bool {
97-
if r.Length() != m.Length() {
102+
lhsLength := r.Length()
103+
rhsLength := m.Length()
104+
if lhsLength != rhsLength {
98105
return false
99106
}
100-
101-
// TODO: Optimize to avoid Iterate looping here by using r.Value.MapRange or similar if it improves performance.
107+
if lhsLength == 0 && rhsLength == 0 {
108+
return true
109+
}
110+
vp := newTempValuePooler()
111+
defer vp.Recycle()
102112
return m.Iterate(func(key string, value Value) bool {
103-
lhsVal, ok := r.Get(key)
113+
_, lhsVal, ok := r.get(key)
104114
if !ok {
105115
return false
106116
}
107-
return Equals(lhsVal, value)
117+
return Equals(vp.NewValueReflect(lhsVal), value)
108118
})
109119
}

value/value.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,9 @@ func ToString(v Value) string {
203203
return fmt.Sprintf("%v", v.AsBool())
204204
case v.IsList():
205205
strs := []string{}
206-
for i := 0; i < v.AsList().Length(); i++ {
207-
strs = append(strs, ToString(v.AsList().At(i)))
206+
list := v.AsList()
207+
for i := 0; i < list.Length(); i++ {
208+
strs = append(strs, ToString(list.At(i)))
208209
}
209210
return "[" + strings.Join(strs, ",") + "]"
210211
case v.IsMap():

value/valuereflect_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,7 @@ func TestReflectList(t *testing.T) {
552552
if m.Length() != tc.length {
553553
t.Errorf("expected list to be of length %d but got %d", tc.length, m.Length())
554554
}
555+
555556
l := m.Length()
556557
iterateResult := make([]interface{}, l)
557558
for i := 0; i < l; i++ {
@@ -560,6 +561,18 @@ func TestReflectList(t *testing.T) {
560561
if !reflect.DeepEqual(iterateResult, tc.expectedIterate) {
561562
t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedIterate, iterateResult)
562563
}
564+
565+
iter := m.Range()
566+
iter.Recycle()
567+
iterateResult = make([]interface{}, l)
568+
for iter.Next() {
569+
i, val := iter.Item()
570+
iterateResult[i] = val.AsString()
571+
}
572+
if !reflect.DeepEqual(iterateResult, tc.expectedIterate) {
573+
t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedIterate, iterateResult)
574+
}
575+
563576
unstructured := rv.Unstructured()
564577
if !reflect.DeepEqual(unstructured, tc.expectedUnstructured) {
565578
t.Errorf("expected iterate to produce %#v but got %#v", tc.expectedUnstructured, unstructured)

0 commit comments

Comments
 (0)