From: korzhao Date: Wed, 2 Aug 2023 09:39:09 +0000 (+0800) Subject: encoding/json: optimize Marshal for maps X-Git-Tag: go1.22rc1~1100 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=4c5dac72029bb3dd4559a2fafe566bed71f22c42;p=gostls13.git encoding/json: optimize Marshal for maps Optimize marshaling of maps by using slices.SortFunc. This drops an unnecessary field from reflectWithString, which also reduces the cost of each swap operation. benchmark old ns/op new ns/op delta BenchmarkMarshalMap-10 228 139 -39.24% benchmark old allocs new allocs delta BenchmarkMarshalMap-10 11 8 -27.27% benchmark old bytes new bytes delta BenchmarkMarshalMap-10 432 232 -46.30% Change-Id: Ic2ba7a1590863c7536305c6f6536372b26ec9b0c Reviewed-on: https://go-review.googlesource.com/c/go/+/515176 Run-TryBot: Ian Lance Taylor Reviewed-by: Daniel Martí Reviewed-by: Joseph Tsai Reviewed-by: qiulaidongfeng <2645477756@qq.com> TryBot-Result: Gopher Robot Reviewed-by: Michael Knyszek Reviewed-by: Dmitri Shuralyov --- diff --git a/src/encoding/json/bench_test.go b/src/encoding/json/bench_test.go index b7e2b6974a..0f080acdbf 100644 --- a/src/encoding/json/bench_test.go +++ b/src/encoding/json/bench_test.go @@ -246,6 +246,22 @@ func BenchmarkMarshalBytesError(b *testing.B) { b.Run("4096", benchMarshalBytesError(4096)) } +func BenchmarkMarshalMap(b *testing.B) { + b.ReportAllocs() + m := map[string]int{ + "key3": 3, + "key2": 2, + "key1": 1, + } + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := Marshal(m); err != nil { + b.Fatal("Marshal:", err) + } + } + }) +} + func BenchmarkCodeDecoder(b *testing.B) { b.ReportAllocs() if codeJSON == nil { diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index 9ba717c9ce..2752fcc86d 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -17,6 +17,7 @@ import ( "fmt" "math" "reflect" + "slices" "sort" "strconv" "strings" @@ -739,16 +740,20 @@ func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { e.WriteByte('{') // Extract and sort the keys. - sv := make([]reflectWithString, v.Len()) - mi := v.MapRange() + var ( + sv = make([]reflectWithString, v.Len()) + mi = v.MapRange() + err error + ) for i := 0; mi.Next(); i++ { - sv[i].k = mi.Key() - sv[i].v = mi.Value() - if err := sv[i].resolve(); err != nil { + if sv[i].ks, err = resolveKeyName(mi.Key()); err != nil { e.error(fmt.Errorf("json: encoding error for type %q: %q", v.Type().String(), err.Error())) } + sv[i].v = mi.Value() } - sort.Slice(sv, func(i, j int) bool { return sv[i].ks < sv[j].ks }) + slices.SortFunc(sv, func(i, j reflectWithString) int { + return strings.Compare(i.ks, j.ks) + }) for i, kv := range sv { if i > 0 { @@ -927,31 +932,26 @@ func typeByIndex(t reflect.Type, index []int) reflect.Type { } type reflectWithString struct { - k reflect.Value v reflect.Value ks string } -func (w *reflectWithString) resolve() error { - if w.k.Kind() == reflect.String { - w.ks = w.k.String() - return nil +func resolveKeyName(k reflect.Value) (string, error) { + if k.Kind() == reflect.String { + return k.String(), nil } - if tm, ok := w.k.Interface().(encoding.TextMarshaler); ok { - if w.k.Kind() == reflect.Pointer && w.k.IsNil() { - return nil + if tm, ok := k.Interface().(encoding.TextMarshaler); ok { + if k.Kind() == reflect.Pointer && k.IsNil() { + return "", nil } buf, err := tm.MarshalText() - w.ks = string(buf) - return err + return string(buf), err } - switch w.k.Kind() { + switch k.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - w.ks = strconv.FormatInt(w.k.Int(), 10) - return nil + return strconv.FormatInt(k.Int(), 10), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - w.ks = strconv.FormatUint(w.k.Uint(), 10) - return nil + return strconv.FormatUint(k.Uint(), 10), nil } panic("unexpected map key type") }