Skip to content

Commit ea08d7c

Browse files
committed
don't use maps for PathElementSet; saves a couple hundred allocations on all benchmarks; needs more testing of the lexical operator
1 parent eaa53bf commit ea08d7c

File tree

3 files changed

+319
-31
lines changed

3 files changed

+319
-31
lines changed

fieldpath/element.go

Lines changed: 130 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,56 @@ type PathElement struct {
4747
Index *int
4848
}
4949

50+
// less provides an order for path elements.
51+
func (e PathElement) less(rhs PathElement) bool {
52+
// For testing the concept, just use the existing string.
53+
//return e.String() < rhs.String()
54+
if e.FieldName != nil {
55+
if rhs.FieldName == nil {
56+
return true
57+
}
58+
return *e.FieldName < *rhs.FieldName
59+
} else if rhs.FieldName != nil {
60+
return false
61+
}
62+
63+
if len(e.Key) != 0 {
64+
if len(rhs.Key) == 0 {
65+
return true
66+
}
67+
return value.FieldCollectionLess(e.Key, rhs.Key)
68+
} else if len(rhs.Key) != 0 {
69+
return false
70+
}
71+
72+
if e.Value != nil {
73+
if rhs.Value == nil {
74+
return true
75+
}
76+
return e.Value.Less(*rhs.Value)
77+
} else if rhs.Value != nil {
78+
return false
79+
}
80+
81+
if e.Index != nil {
82+
if rhs.Index == nil {
83+
return true
84+
}
85+
return *e.Index < *rhs.Index
86+
} else if rhs.Index != nil {
87+
// Yes, I know the next statement is the same. But this way
88+
// the obvious way of extending the function wil be bug-free.
89+
return false
90+
}
91+
92+
return false
93+
}
94+
95+
// Equal returns true if both path elements are equal.
96+
func (e PathElement) Equal(rhs PathElement) bool {
97+
return !e.less(rhs) && !rhs.less(e)
98+
}
99+
50100
// String presents the path element as a human-readable string.
51101
func (e PathElement) String() string {
52102
switch {
@@ -94,60 +144,103 @@ func KeyByFields(nameValues ...interface{}) []value.Field {
94144
type PathElementSet struct {
95145
// The strange construction is because there's no way to test
96146
// PathElements for equality (it can't be used as a key for a map).
97-
members map[string]PathElement
147+
members sortedPathElements
98148
}
99149

150+
type sortedPathElements []PathElement
151+
152+
func (spe sortedPathElements) Len() int { return len(spe) }
153+
func (spe sortedPathElements) Less(i, j int) bool { return spe[i].less(spe[j]) }
154+
func (spe sortedPathElements) Swap(i, j int) { spe[i], spe[j] = spe[j], spe[i] }
155+
100156
// Insert adds pe to the set.
101157
func (s *PathElementSet) Insert(pe PathElement) {
102-
serialized := pe.String()
103-
if s.members == nil {
104-
s.members = map[string]PathElement{
105-
serialized: pe,
106-
}
158+
loc := sort.Search(len(s.members), func(i int) bool {
159+
//return !pe.less(s.members[i])
160+
return !s.members[i].less(pe)
161+
})
162+
if loc == len(s.members) {
163+
s.members = append(s.members, pe)
107164
return
108165
}
109-
if _, ok := s.members[serialized]; !ok {
110-
s.members[serialized] = pe
166+
if s.members[loc].Equal(pe) {
167+
return
168+
}
169+
n := len(s.members) - 1
170+
s.members = append(s.members, s.members[n])
171+
for n > loc {
172+
s.members[n] = s.members[n-1]
173+
n--
111174
}
175+
s.members[loc] = pe
112176
}
113177

114178
// Union returns a set containing elements that appear in either s or s2.
115179
func (s *PathElementSet) Union(s2 *PathElementSet) *PathElementSet {
116-
out := &PathElementSet{
117-
members: map[string]PathElement{},
180+
out := &PathElementSet{}
181+
182+
i, j := 0, 0
183+
for i < len(s.members) && j < len(s2.members) {
184+
if s.members[i].less(s2.members[j]) {
185+
out.members = append(out.members, s.members[i])
186+
i++
187+
} else {
188+
out.members = append(out.members, s2.members[j])
189+
if !s2.members[j].less(s.members[i]) {
190+
i++
191+
}
192+
j++
193+
}
118194
}
119-
for k, v := range s.members {
120-
out.members[k] = v
195+
196+
if i < len(s.members) {
197+
out.members = append(out.members, s.members[i:]...)
121198
}
122-
for k, v := range s2.members {
123-
out.members[k] = v
199+
if j < len(s2.members) {
200+
out.members = append(out.members, s2.members[j:]...)
124201
}
125202
return out
126203
}
127204

128205
// Intersection returns a set containing elements which appear in both s and s2.
129206
func (s *PathElementSet) Intersection(s2 *PathElementSet) *PathElementSet {
130-
out := &PathElementSet{
131-
members: map[string]PathElement{},
132-
}
133-
for k, v := range s.members {
134-
if _, ok := s2.members[k]; ok {
135-
out.members[k] = v
207+
out := &PathElementSet{}
208+
209+
i, j := 0, 0
210+
for i < len(s.members) && j < len(s2.members) {
211+
if s.members[i].less(s2.members[j]) {
212+
i++
213+
} else {
214+
if !s2.members[j].less(s.members[i]) {
215+
out.members = append(out.members, s.members[i])
216+
i++
217+
}
218+
j++
136219
}
137220
}
221+
138222
return out
139223
}
140224

141225
// Difference returns a set containing elements which appear in s but not in s2.
142226
func (s *PathElementSet) Difference(s2 *PathElementSet) *PathElementSet {
143-
out := &PathElementSet{
144-
members: map[string]PathElement{},
145-
}
146-
for k, v := range s.members {
147-
if _, ok := s2.members[k]; !ok {
148-
out.members[k] = v
227+
out := &PathElementSet{}
228+
229+
i, j := 0, 0
230+
for i < len(s.members) && j < len(s2.members) {
231+
if s.members[i].less(s2.members[j]) {
232+
out.members = append(out.members, s.members[i])
233+
i++
234+
} else {
235+
if !s2.members[j].less(s.members[i]) {
236+
i++
237+
}
238+
j++
149239
}
150240
}
241+
if i < len(s.members) {
242+
out.members = append(out.members, s.members[i:]...)
243+
}
151244
return out
152245
}
153246

@@ -156,11 +249,17 @@ func (s *PathElementSet) Size() int { return len(s.members) }
156249

157250
// Has returns true if pe is a member of the set.
158251
func (s *PathElementSet) Has(pe PathElement) bool {
159-
if s.members == nil {
252+
loc := sort.Search(len(s.members), func(i int) bool {
253+
return !s.members[i].less(pe)
254+
//return !pe.less(s.members[i])
255+
})
256+
if loc == len(s.members) {
160257
return false
161258
}
162-
_, ok := s.members[pe.String()]
163-
return ok
259+
if s.members[loc].Equal(pe) {
260+
return true
261+
}
262+
return false
164263
}
165264

166265
// Equals returns true if s and s2 have exactly the same members.
@@ -169,14 +268,14 @@ func (s *PathElementSet) Equals(s2 *PathElementSet) bool {
169268
return false
170269
}
171270
for k := range s.members {
172-
if _, ok := s2.members[k]; !ok {
271+
if !s.members[k].Equal(s2.members[k]) {
173272
return false
174273
}
175274
}
176275
return true
177276
}
178277

179-
// Iterate calls f for each PathElement in the set.
278+
// Iterate calls f for each PathElement in the set. The order is deterministic.
180279
func (s *PathElementSet) Iterate(f func(PathElement)) {
181280
for _, pe := range s.members {
182281
f(pe)

fieldpath/element_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,34 @@ func TestPathElementSet(t *testing.T) {
2828
if s2.Equals(s) {
2929
t.Errorf("unequal sets should not equal")
3030
}
31+
if !s2.Has(PathElement{}) {
32+
t.Errorf("expected to have something: %#v", s2)
33+
}
34+
35+
n1 := "aoeu"
36+
n2 := "asdf"
37+
s2.Insert(PathElement{FieldName: &n1})
38+
if !s2.Has(PathElement{FieldName: &n1}) {
39+
t.Errorf("expected to have something: %#v", s2)
40+
}
41+
if s2.Has(PathElement{FieldName: &n2}) {
42+
t.Errorf("expected to not have something: %#v", s2)
43+
}
44+
45+
s2.Insert(PathElement{FieldName: &n2})
46+
expected := []*string{&n1, &n2, nil}
47+
i := 0
48+
s2.Iterate(func(pe PathElement) {
49+
e, a := expected[i], pe.FieldName
50+
if e == nil || a == nil {
51+
if e != a {
52+
t.Errorf("index %v wanted %#v, got %#v", i, e, a)
53+
}
54+
} else {
55+
if *e != *a {
56+
t.Errorf("index %v wanted %#v, got %#v", i, *e, *a)
57+
}
58+
}
59+
i++
60+
})
3161
}

0 commit comments

Comments
 (0)