-
Notifications
You must be signed in to change notification settings - Fork 104
Closed
Labels
I4No visible changesNo visible changesS2Regular significanceRegular significanceU4Nothing urgentNothing urgentenhancementImproving existing functionalityImproving existing functionalitygoGo language relatedGo language related
Milestone
Description
It's not yet released, so the issue is to be updated, but there is one thing I've discovered with go-ordered-json which affects #3089 handling in that some sorting changes better be postponed till 1.23 is live (and we can do this when it's the default for builds, not minimal supported). A relatively simple change there:
diff --git a/encode.go b/encode.go
index f971e42..24d207b 100644
--- a/encode.go
+++ b/encode.go
@@ -18,7 +18,7 @@ import (
"math"
"reflect"
"runtime"
- "sort"
+ "slices"
"strconv"
"strings"
"sync"
@@ -680,7 +680,7 @@ func (me *mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
e.error(&MarshalerError{v.Type(), err})
}
}
- sort.Slice(sv, func(i, j int) bool { return sv[i].s < sv[j].s })
+ slices.SortFunc(sv, func(a, b reflectWithString) int { return strings.Compare(a.s, b.s) })
for i, kv := range sv {
if i > 0 {
@@ -1110,23 +1110,16 @@ func fillField(f field) field {
return f
}
-// byIndex sorts field by index sequence.
-type byIndex []field
-
-func (x byIndex) Len() int { return len(x) }
-
-func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
-
-func (x byIndex) Less(i, j int) bool {
- for k, xik := range x[i].index {
- if k >= len(x[j].index) {
- return false
+func cmpFieldsByIndex(a, b field) int {
+ for k, ak := range a.index {
+ if k >= len(b.index) {
+ return 1
}
- if xik != x[j].index[k] {
- return xik < x[j].index[k]
+ if ak != b.index[k] {
+ return ak - b.index[k]
}
}
- return len(x[i].index) < len(x[j].index)
+ return len(a.index) - len(b.index)
}
// typeFields returns a list of fields that JSON should recognize for the given type.
@@ -1228,21 +1221,24 @@ func typeFields(t reflect.Type) []field {
}
}
- sort.Slice(fields, func(i, j int) bool {
- x := fields
+ slices.SortFunc(fields, func(a, b field) int {
// sort field by name, breaking ties with depth, then
// breaking ties with "name came from json tag", then
// breaking ties with index sequence.
- if x[i].name != x[j].name {
- return x[i].name < x[j].name
+ var res = strings.Compare(a.name, b.name)
+ if res != 0 {
+ return res
+ }
+ if len(a.index) != len(b.index) {
+ return len(a.index) - len(b.index)
}
- if len(x[i].index) != len(x[j].index) {
- return len(x[i].index) < len(x[j].index)
+ if b.tag == false && a.tag == true {
+ return -1
}
- if x[i].tag != x[j].tag {
- return x[i].tag
+ if b.tag == true && a.tag == false {
+ return 1
}
- return byIndex(x).Less(i, j)
+ return cmpFieldsByIndex(a, b)
})
// Delete all fields that are hidden by the Go rules for embedded fields,
@@ -1274,7 +1270,7 @@ func typeFields(t reflect.Type) []field {
}
fields = out
- sort.Sort(byIndex(fields))
+ slices.SortFunc(fields, cmpFieldsByIndex)
return fields
}
that is expected to be beneficial (no conversions to interface, no slice checks, stronger comparison function), leads to serious performance degradation:
goos: linux
goarch: amd64
pkg: github.com/nspcc-dev/go-ordered-json
cpu: AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics
│ slices.pre │ slices.post │
│ sec/op │ sec/op vs base │
CodeEncoder-16 675.7µ ± 1% 793.7µ ± 1% +17.46% (p=0.000 n=10)
CodeMarshal-16 862.3µ ± 1% 928.5µ ± 1% +7.67% (p=0.000 n=10)
CodeDecoder-16 3.720m ± 2% 4.287m ± 1% +15.25% (p=0.000 n=10)
DecoderStream-16 86.03n ± 1% 90.56n ± 1% +5.26% (p=0.000 n=10)
CodeUnmarshal-16 3.809m ± 1% 5.159m ± 13% +35.44% (p=0.000 n=10)
CodeUnmarshalReuse-16 4.022m ± 10% 4.775m ± 3% +18.73% (p=0.000 n=10)
UnmarshalString-16 46.14n ± 1% 57.25n ± 1% +24.05% (p=0.000 n=10)
UnmarshalFloat64-16 42.96n ± 2% 53.66n ± 1% +24.90% (p=0.000 n=10)
UnmarshalInt64-16 36.69n ± 2% 46.07n ± 2% +25.57% (p=0.000 n=10)
Issue10335-16 54.79n ± 2% 70.75n ± 1% +29.14% (p=0.000 n=10)
Unmapped-16 142.1n ± 1% 212.0n ± 2% +49.14% (p=0.000 n=10)
NumberIsValid-16 8.996n ± 2% 9.850n ± 6% +9.48% (p=0.000 n=10)
NumberIsValidRegexp-16 194.1n ± 1% 197.0n ± 4% ~ (p=0.210 n=10)
SkipValue-16 7.786m ± 2% 7.598m ± 4% ~ (p=0.105 n=10)
EncoderEncode-16 20.07n ± 1% 22.27n ± 1% +11.01% (p=0.000 n=10)
geomean 3.770µ 4.427µ +17.43%
│ slices.pre │ slices.post │
│ B/s │ B/s vs base │
CodeEncoder-16 2.675Gi ± 1% 2.277Gi ± 1% -14.87% (p=0.000 n=10)
CodeMarshal-16 2.096Gi ± 1% 1.946Gi ± 1% -7.13% (p=0.000 n=10)
CodeDecoder-16 497.5Mi ± 2% 431.6Mi ± 1% -13.23% (p=0.000 n=10)
CodeUnmarshal-16 485.9Mi ± 1% 358.8Mi ± 15% -26.16% (p=0.000 n=10)
SkipValue-16 266.0Mi ± 3% 269.7Mi ± 1% ~ (p=0.218 n=10)
geomean 823.1Mi 720.4Mi -12.48%
│ slices.pre │ slices.post │
│ B/op │ B/op vs base │
CodeEncoder-16 3.500 ± 71% 3.500 ± 100% ~ (p=0.892 n=10)
CodeMarshal-16 4.000Mi ± 0% 4.000Mi ± 0% ~ (p=0.331 n=10)
CodeDecoder-16 1.911Mi ± 2% 1.981Mi ± 1% +3.64% (p=0.000 n=10)
DecoderStream-16 8.000 ± 0% 8.000 ± 0% ~ (p=1.000 n=10) ¹
CodeUnmarshal-16 2.903Mi ± 0% 2.903Mi ± 0% ~ (p=0.402 n=10)
CodeUnmarshalReuse-16 1.614Mi ± 0% 1.626Mi ± 0% +0.78% (p=0.000 n=10)
UnmarshalString-16 304.0 ± 0% 304.0 ± 0% ~ (p=1.000 n=10) ¹
UnmarshalFloat64-16 292.0 ± 0% 292.0 ± 0% ~ (p=1.000 n=10) ¹
UnmarshalInt64-16 288.0 ± 0% 288.0 ± 0% ~ (p=1.000 n=10) ¹
Issue10335-16 312.0 ± 0% 312.0 ± 0% ~ (p=1.000 n=10) ¹
Unmapped-16 344.0 ± 0% 344.0 ± 0% ~ (p=1.000 n=10) ¹
NumberIsValid-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
NumberIsValidRegexp-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
SkipValue-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10)
EncoderEncode-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
geomean ² +0.29% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
│ slices.pre │ slices.post │
│ allocs/op │ allocs/op vs base │
CodeEncoder-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
CodeMarshal-16 17.00 ± 0% 17.00 ± 0% ~ (p=1.000 n=10) ¹
CodeDecoder-16 77.09k ± 0% 77.25k ± 0% +0.21% (p=0.000 n=10)
DecoderStream-16 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
CodeUnmarshal-16 92.66k ± 0% 92.66k ± 0% ~ (p=1.000 n=10) ¹
CodeUnmarshalReuse-16 77.19k ± 0% 77.34k ± 0% +0.19% (p=0.000 n=10)
UnmarshalString-16 2.000 ± 0% 2.000 ± 0% ~ (p=1.000 n=10) ¹
UnmarshalFloat64-16 2.000 ± 0% 2.000 ± 0% ~ (p=1.000 n=10) ¹
UnmarshalInt64-16 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹
Issue10335-16 3.000 ± 0% 3.000 ± 0% ~ (p=1.000 n=10) ¹
Unmapped-16 4.000 ± 0% 4.000 ± 0% ~ (p=1.000 n=10) ¹
NumberIsValid-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
NumberIsValidRegexp-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
SkipValue-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
EncoderEncode-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
geomean ² +0.03% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
Because strings.Compare is intentionally deoptimized (https://pkg.go.dev/strings#Compare, https://go101.org/blog/2022-10-01-three-way-string-comparison.html). This is to change soon with golang/go@fd999fd, but for now that's what it is, good old sort.Slice wins.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
I4No visible changesNo visible changesS2Regular significanceRegular significanceU4Nothing urgentNothing urgentenhancementImproving existing functionalityImproving existing functionalitygoGo language relatedGo language related