Skip to content

Commit 6c869cc

Browse files
committed
Add (*Set).FromJSON benchmarks and switch to post-accumulation sort.
Set deserialization with children in reverse order performs an insertion sort. Sets serialized by structured-merge-diff are sorted, but in practice the serialization of sets is controlled by clients and they can (intentionally or not) exercise this worst case. Switching to a bulk insert followed by a sort appears to perform no worse in the best case (sorted) and is significantly better in the worst case: │ master.bench │ bulk.bench │ │ sec/op │ sec/op vs base │ SetFromJSON/children_in_ascending_order 1.338m ± 0% 1.316m ± 1% -1.60% (p=0.001 n=10) SetFromJSON/children_in_descending_order 20.520m ± 1% 1.356m ± 1% -93.39% (p=0.000 n=10) SetFromJSON/grandchildren_in_ascending_order 3.001m ± 1% 2.935m ± 1% -2.20% (p=0.000 n=10) SetFromJSON/grandchildren_in_descending_order 39.963m ± 0% 2.923m ± 1% -92.69% (p=0.000 n=10) geomean 7.575m 1.978m -73.88% │ master.bench │ bulk.bench │ │ B/op │ B/op vs base │ SetFromJSON/children_in_ascending_order 1.320Mi ± 0% 1.320Mi ± 0% ~ (p=0.303 n=10) SetFromJSON/children_in_descending_order 1.320Mi ± 0% 1.320Mi ± 0% +0.00% (p=0.000 n=10) SetFromJSON/grandchildren_in_ascending_order 2.512Mi ± 0% 2.512Mi ± 0% +0.00% (p=0.001 n=10) SetFromJSON/grandchildren_in_descending_order 2.908Mi ± 0% 2.512Mi ± 0% -13.61% (p=0.000 n=10) geomean 1.889Mi 1.821Mi -3.59% │ master.bench │ bulk.bench │ │ allocs/op │ allocs/op vs base │ SetFromJSON/children_in_ascending_order 25.99k ± 0% 25.99k ± 0% ~ (p=1.000 n=10) ¹ SetFromJSON/children_in_descending_order 25.99k ± 0% 25.99k ± 0% ~ (p=1.000 n=10) ¹ SetFromJSON/grandchildren_in_ascending_order 69.24k ± 0% 69.24k ± 0% ~ (p=1.000 n=10) ¹ SetFromJSON/grandchildren_in_descending_order 77.89k ± 0% 69.24k ± 0% -11.10% (p=0.000 n=10) geomean 43.68k 42.42k -2.90% ¹ all samples are equal
1 parent 3392408 commit 6c869cc

File tree

6 files changed

+42
-18
lines changed

6 files changed

+42
-18
lines changed

fieldpath/serialize.go

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package fieldpath
1919
import (
2020
"bytes"
2121
"io"
22+
"slices"
2223
"unsafe"
2324

2425
jsoniter "github.com/json-iterator/go"
@@ -204,34 +205,25 @@ func readIterV1(iter *jsoniter.Iterator) (children *Set, isMember bool) {
204205
if children == nil {
205206
children = &Set{}
206207
}
207-
m := &children.Members.members
208-
// Since we expect that most of the time these will have been
209-
// serialized in the right order, we just verify that and append.
210-
appendOK := len(*m) == 0 || (*m)[len(*m)-1].Less(pe)
211-
if appendOK {
212-
*m = append(*m, pe)
213-
} else {
214-
children.Members.Insert(pe)
215-
}
208+
children.Members.members = append(children.Members.members, pe)
216209
}
217210
if grandchildren != nil {
218211
if children == nil {
219212
children = &Set{}
220213
}
221-
// Since we expect that most of the time these will have been
222-
// serialized in the right order, we just verify that and append.
223-
m := &children.Children.members
224-
appendOK := len(*m) == 0 || (*m)[len(*m)-1].pathElement.Less(pe)
225-
if appendOK {
226-
*m = append(*m, setNode{pe, grandchildren})
227-
} else {
228-
*children.Children.Descend(pe) = *grandchildren
229-
}
214+
children.Children.members = append(children.Children.members, setNode{pe, grandchildren})
230215
}
231216
return true
232217
})
233218
if children == nil {
234219
isMember = true
220+
} else {
221+
slices.SortFunc(children.Members.members, func(a, b PathElement) int {
222+
return a.Compare(b)
223+
})
224+
slices.SortFunc(children.Children.members, func(a, b setNode) int {
225+
return a.pathElement.Compare(b.pathElement)
226+
})
235227
}
236228

237229
return children, isMember

fieldpath/serialize_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package fieldpath_test
1919
import (
2020
"bytes"
2121
"fmt"
22+
"os"
2223
"strings"
2324
"testing"
2425

@@ -330,3 +331,30 @@ func TestDropUnknown(t *testing.T) {
330331
t.Errorf("Failed;\ngot: %s\nwant: %s\n", b, expect)
331332
}
332333
}
334+
335+
func BenchmarkSetFromJSON(b *testing.B) {
336+
for _, tc := range []struct {
337+
name string
338+
fixture string
339+
}{
340+
{"children in ascending order", "testdata/set_ascending.json"},
341+
{"children in descending order", "testdata/set_descending.json"},
342+
{"grandchildren in ascending order", "testdata/set_ascending_grandchildren.json"},
343+
{"grandchildren in descending order", "testdata/set_descending_grandchildren.json"},
344+
} {
345+
b.Run(tc.name, func(b *testing.B) {
346+
data, err := os.ReadFile(tc.fixture)
347+
if err != nil {
348+
b.Fatal(err)
349+
}
350+
351+
b.ResetTimer()
352+
for range b.N {
353+
x := NewSet()
354+
if err := x.FromJSON(bytes.NewReader(data)); err != nil {
355+
b.Fatal(err)
356+
}
357+
}
358+
})
359+
}
360+
}

fieldpath/testdata/set_ascending.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

fieldpath/testdata/set_ascending_grandchildren.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

fieldpath/testdata/set_descending.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

fieldpath/testdata/set_descending_grandchildren.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)