Skip to content

Commit e660f95

Browse files
authored
Merge pull request #114 from apelisse/perf/pathelement-set
Use PathElementSet and similar to index PathElement
2 parents ae447d5 + 5d9b53d commit e660f95

File tree

5 files changed

+168
-28
lines changed

5 files changed

+168
-28
lines changed

fieldpath/element.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@ type PathElementSet struct {
143143
members sortedPathElements
144144
}
145145

146+
func MakePathElementSet(size int) PathElementSet {
147+
return PathElementSet{
148+
members: make(sortedPathElements, 0, size),
149+
}
150+
}
151+
146152
type sortedPathElements []PathElement
147153

148154
// Implement the sort interface; this would permit bulk creation, which would

fieldpath/pathelementmap.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package fieldpath
18+
19+
import (
20+
"sort"
21+
22+
"sigs.k8s.io/structured-merge-diff/value"
23+
)
24+
25+
// PathElementValueMap is a map from PathElement to value.Value.
26+
//
27+
// TODO(apelisse): We have multiple very similar implementation of this
28+
// for PathElementSet and SetNodeMap, so we could probably share the
29+
// code.
30+
type PathElementValueMap struct {
31+
members sortedPathElementValues
32+
}
33+
34+
func MakePathElementValueMap(size int) PathElementValueMap {
35+
return PathElementValueMap{
36+
members: make(sortedPathElementValues, 0, size),
37+
}
38+
}
39+
40+
type pathElementValue struct {
41+
PathElement PathElement
42+
Value value.Value
43+
}
44+
45+
type sortedPathElementValues []pathElementValue
46+
47+
// Implement the sort interface; this would permit bulk creation, which would
48+
// be faster than doing it one at a time via Insert.
49+
func (spev sortedPathElementValues) Len() int { return len(spev) }
50+
func (spev sortedPathElementValues) Less(i, j int) bool {
51+
return spev[i].PathElement.Less(spev[j].PathElement)
52+
}
53+
func (spev sortedPathElementValues) Swap(i, j int) { spev[i], spev[j] = spev[j], spev[i] }
54+
55+
// Insert adds the pathelement and associated value in the map.
56+
func (s *PathElementValueMap) Insert(pe PathElement, v value.Value) {
57+
loc := sort.Search(len(s.members), func(i int) bool {
58+
return !s.members[i].PathElement.Less(pe)
59+
})
60+
if loc == len(s.members) {
61+
s.members = append(s.members, pathElementValue{pe, v})
62+
return
63+
}
64+
if s.members[loc].PathElement.Equals(pe) {
65+
return
66+
}
67+
s.members = append(s.members, pathElementValue{})
68+
copy(s.members[loc+1:], s.members[loc:])
69+
s.members[loc] = pathElementValue{pe, v}
70+
}
71+
72+
// Get retrieves the value associated with the given PathElement from the map.
73+
// (nil, false) is returned if there is no such PathElement.
74+
func (s *PathElementValueMap) Get(pe PathElement) (value.Value, bool) {
75+
loc := sort.Search(len(s.members), func(i int) bool {
76+
return !s.members[i].PathElement.Less(pe)
77+
})
78+
if loc == len(s.members) {
79+
return value.Value{}, false
80+
}
81+
if s.members[loc].PathElement.Equals(pe) {
82+
return s.members[loc].Value, true
83+
}
84+
return value.Value{}, false
85+
}

fieldpath/pathelementmap_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package fieldpath
18+
19+
import (
20+
"testing"
21+
22+
"sigs.k8s.io/structured-merge-diff/value"
23+
)
24+
25+
func TestPathElementValueMap(t *testing.T) {
26+
m := PathElementValueMap{}
27+
28+
if _, ok := m.Get(PathElement{FieldName: strptr("onion")}); ok {
29+
t.Fatal("Unexpected path-element found in empty map")
30+
}
31+
32+
m.Insert(PathElement{FieldName: strptr("carrot")}, value.StringValue("knife"))
33+
m.Insert(PathElement{FieldName: strptr("chive")}, value.IntValue(2))
34+
35+
if _, ok := m.Get(PathElement{FieldName: strptr("onion")}); ok {
36+
t.Fatal("Unexpected path-element in map")
37+
}
38+
39+
if val, ok := m.Get(PathElement{FieldName: strptr("carrot")}); !ok {
40+
t.Fatal("Missing path-element in map")
41+
} else if !val.Equals(value.StringValue("knife")) {
42+
t.Fatalf("Unexpected value found: %#v", val)
43+
}
44+
45+
if val, ok := m.Get(PathElement{FieldName: strptr("chive")}); !ok {
46+
t.Fatal("Missing path-element in map")
47+
} else if !val.Equals(value.IntValue(2)) {
48+
t.Fatalf("Unexpected value found: %#v", val)
49+
}
50+
}

typed/merge.go

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,9 @@ func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs *value.List) (er
168168
rhsOrder := []fieldpath.PathElement{}
169169

170170
// First, collect all RHS children.
171-
observedRHS := map[string]value.Value{}
171+
var observedRHS fieldpath.PathElementValueMap
172172
if rhs != nil {
173+
observedRHS = fieldpath.MakePathElementValueMap(len(rhs.Items))
173174
for i, child := range rhs.Items {
174175
pe, err := listItemToPathElement(t, i, child)
175176
if err != nil {
@@ -179,18 +180,18 @@ func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs *value.List) (er
179180
// this element.
180181
continue
181182
}
182-
keyStr := pe.String()
183-
if _, found := observedRHS[keyStr]; found {
184-
errs = append(errs, w.errorf("rhs: duplicate entries for key %v", keyStr)...)
183+
if _, ok := observedRHS.Get(pe); ok {
184+
errs = append(errs, w.errorf("rhs: duplicate entries for key %v", pe.String())...)
185185
}
186-
observedRHS[keyStr] = child
186+
observedRHS.Insert(pe, child)
187187
rhsOrder = append(rhsOrder, pe)
188188
}
189189
}
190190

191191
// Then merge with LHS children.
192-
observedLHS := map[string]struct{}{}
192+
var observedLHS fieldpath.PathElementSet
193193
if lhs != nil {
194+
observedLHS = fieldpath.MakePathElementSet(len(lhs.Items))
194195
for i, child := range lhs.Items {
195196
pe, err := listItemToPathElement(t, i, child)
196197
if err != nil {
@@ -200,15 +201,14 @@ func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs *value.List) (er
200201
// this element.
201202
continue
202203
}
203-
keyStr := pe.String()
204-
if _, found := observedLHS[keyStr]; found {
205-
errs = append(errs, w.errorf("lhs: duplicate entries for key %v", keyStr)...)
204+
if observedLHS.Has(pe) {
205+
errs = append(errs, w.errorf("lhs: duplicate entries for key %v", pe.String())...)
206206
continue
207207
}
208-
observedLHS[keyStr] = struct{}{}
208+
observedLHS.Insert(pe)
209209
w2 := w.prepareDescent(pe, t.ElementType)
210210
w2.lhs = &child
211-
if rchild, ok := observedRHS[keyStr]; ok {
211+
if rchild, ok := observedRHS.Get(pe); ok {
212212
w2.rhs = &rchild
213213
}
214214
if newErrs := w2.merge(); len(newErrs) > 0 {
@@ -217,22 +217,22 @@ func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs *value.List) (er
217217
out.Items = append(out.Items, *w2.out)
218218
}
219219
w.finishDescent(w2)
220-
// Keep track of children that have been handled
221-
delete(observedRHS, keyStr)
222220
}
223221
}
224222

225-
for _, rhsToCheck := range rhsOrder {
226-
if unmergedChild, ok := observedRHS[rhsToCheck.String()]; ok {
227-
w2 := w.prepareDescent(rhsToCheck, t.ElementType)
228-
w2.rhs = &unmergedChild
229-
if newErrs := w2.merge(); len(newErrs) > 0 {
230-
errs = append(errs, newErrs...)
231-
} else if w2.out != nil {
232-
out.Items = append(out.Items, *w2.out)
233-
}
234-
w.finishDescent(w2)
223+
for _, pe := range rhsOrder {
224+
if observedLHS.Has(pe) {
225+
continue
226+
}
227+
value, _ := observedRHS.Get(pe)
228+
w2 := w.prepareDescent(pe, t.ElementType)
229+
w2.rhs = &value
230+
if newErrs := w2.merge(); len(newErrs) > 0 {
231+
errs = append(errs, newErrs...)
232+
} else if w2.out != nil {
233+
out.Items = append(out.Items, *w2.out)
235234
}
235+
w.finishDescent(w2)
236236
}
237237

238238
if len(out.Items) > 0 {

typed/validate.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func (v *validatingObjectWalker) doScalar(t *schema.Scalar) ValidationErrors {
142142
}
143143

144144
func (v *validatingObjectWalker) visitListItems(t *schema.List, list *value.List) (errs ValidationErrors) {
145-
observedKeys := map[string]struct{}{}
145+
observedKeys := fieldpath.MakePathElementSet(len(list.Items))
146146
for i, child := range list.Items {
147147
pe, err := listItemToPathElement(t, i, child)
148148
if err != nil {
@@ -152,11 +152,10 @@ func (v *validatingObjectWalker) visitListItems(t *schema.List, list *value.List
152152
// this element.
153153
continue
154154
}
155-
keyStr := pe.String()
156-
if _, found := observedKeys[keyStr]; found {
157-
errs = append(errs, v.errorf("duplicate entries for key %v", keyStr)...)
155+
if observedKeys.Has(pe) {
156+
errs = append(errs, v.errorf("duplicate entries for key %v", pe.String())...)
158157
}
159-
observedKeys[keyStr] = struct{}{}
158+
observedKeys.Insert(pe)
160159
v2 := v.prepareDescent(pe, t.ElementType)
161160
v2.value = child
162161
errs = append(errs, v2.validate()...)

0 commit comments

Comments
 (0)