Skip to content

Commit 8f1be3a

Browse files
committed
reduce allocations for small maps
1 parent 978ad1e commit 8f1be3a

File tree

3 files changed

+74
-11
lines changed

3 files changed

+74
-11
lines changed

fieldpath/serialize.go

+19-3
Original file line numberDiff line numberDiff line change
@@ -143,19 +143,35 @@ func readIter_v1(iter *jsoniter.Iterator) (children *Set, isMember bool) {
143143
if children == nil {
144144
children = &Set{}
145145
}
146-
// TODO: append & sort afterwards
147-
children.Members.Insert(pe)
146+
m := &children.Members.members
147+
// Since we expect that most of the time these will have been
148+
// serialized in the right order, we just verify that and append.
149+
appendOK := len(*m) == 0 || (*m)[len(*m)-1].Less(pe)
150+
if appendOK {
151+
*m = append(*m, pe)
152+
} else {
153+
children.Members.Insert(pe)
154+
}
148155
}
149156
if grandchildren != nil {
150157
if children == nil {
151158
children = &Set{}
152159
}
153-
*children.Children.Descend(pe) = *grandchildren
160+
// Since we expect that most of the time these will have been
161+
// serialized in the right order, we just verify that and append.
162+
m := &children.Children.members
163+
appendOK := len(*m) == 0 || (*m)[len(*m)-1].pathElement.Less(pe)
164+
if appendOK {
165+
*m = append(*m, setNode{pe, grandchildren})
166+
} else {
167+
*children.Children.Descend(pe) = *grandchildren
168+
}
154169
}
155170
return true
156171
})
157172
if children == nil {
158173
isMember = true
159174
}
175+
160176
return children, isMember
161177
}

fieldpath/set.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,17 @@ type setNode struct {
173173

174174
// SetNodeMap is a map of PathElement to subset.
175175
type SetNodeMap struct {
176-
members []setNode
176+
members sortedSetNode
177177
}
178178

179+
type sortedSetNode []setNode
180+
181+
// Implement the sort interface; this would permit bulk creation, which would
182+
// be faster than doing it one at a time via Insert.
183+
func (s sortedSetNode) Len() int { return len(s) }
184+
func (s sortedSetNode) Less(i, j int) bool { return s[i].pathElement.Less(s[j].pathElement) }
185+
func (s sortedSetNode) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
186+
179187
// Descend adds pe to the set if necessary, returning the associated subset.
180188
func (s *SetNodeMap) Descend(pe PathElement) *Set {
181189
loc := sort.Search(len(s.members), func(i int) bool {

value/value.go

+46-7
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ type Map struct {
173173
order []int
174174
}
175175

176-
func (m *Map) computeOrder() {
176+
func (m *Map) computeOrder() []int {
177177
if len(m.order) != len(m.Items) {
178178
m.order = make([]int, len(m.Items))
179179
for i := range m.order {
@@ -183,28 +183,67 @@ func (m *Map) computeOrder() {
183183
return m.Items[m.order[i]].Name < m.Items[m.order[j]].Name
184184
})
185185
}
186+
return m.order
186187
}
187188

188189
// Less compares two maps lexically.
189190
func (m *Map) Less(rhs *Map) bool {
190-
m.computeOrder()
191-
rhs.computeOrder()
191+
var noAllocL, noAllocR [2]int
192+
var morder, rorder []int
193+
194+
// For very short maps (<2 elements) this permits us to avoid
195+
// allocating the order array. We could make this accomodate larger
196+
// maps, but 2 items should be enough to cover most path element
197+
// comparisons, and at some point there will be diminishing returns.
198+
// This has a large effect on the path element deserialization test,
199+
// because everything is sorted / compared, but only once.
200+
switch len(m.Items) {
201+
case 0:
202+
morder = noAllocL[0:0]
203+
case 1:
204+
morder = noAllocL[0:1]
205+
case 2:
206+
morder = noAllocL[0:2]
207+
if m.Items[0].Name > m.Items[1].Name {
208+
morder[0] = 1
209+
} else {
210+
morder[1] = 1
211+
}
212+
default:
213+
morder = m.computeOrder()
214+
}
215+
216+
switch len(rhs.Items) {
217+
case 0:
218+
rorder = noAllocR[0:0]
219+
case 1:
220+
rorder = noAllocR[0:1]
221+
case 2:
222+
rorder = noAllocR[0:2]
223+
if rhs.Items[0].Name > rhs.Items[1].Name {
224+
rorder[0] = 1
225+
} else {
226+
rorder[1] = 1
227+
}
228+
default:
229+
rorder = rhs.computeOrder()
230+
}
192231

193232
i := 0
194233
for {
195-
if i >= len(m.order) && i >= len(rhs.order) {
234+
if i >= len(morder) && i >= len(rorder) {
196235
// Maps are the same length and all items are equal.
197236
return false
198237
}
199-
if i >= len(m.order) {
238+
if i >= len(morder) {
200239
// LHS is shorter.
201240
return true
202241
}
203-
if i >= len(rhs.order) {
242+
if i >= len(rorder) {
204243
// RHS is shorter.
205244
return false
206245
}
207-
fa, fb := &m.Items[m.order[i]], &rhs.Items[rhs.order[i]]
246+
fa, fb := &m.Items[morder[i]], &rhs.Items[rorder[i]]
208247
if fa.Name != fb.Name {
209248
// the map having the field name that sorts lexically less is "less"
210249
return fa.Name < fb.Name

0 commit comments

Comments
 (0)