]> Cypherpunks.ru repositories - gostls13.git/commitdiff
encoding/json: optimize Marshal for maps
authorkorzhao <korzhao95@gmail.com>
Wed, 2 Aug 2023 09:39:09 +0000 (17:39 +0800)
committerDaniel Martí <mvdan@mvdan.cc>
Fri, 25 Aug 2023 08:50:07 +0000 (08:50 +0000)
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 <iant@golang.org>
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
Reviewed-by: Joseph Tsai <joetsai@digital-static.net>
Reviewed-by: qiulaidongfeng <2645477756@qq.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
src/encoding/json/bench_test.go
src/encoding/json/encode.go

index b7e2b6974ae1d1acfee980fcade91afa894d3084..0f080acdbf2d6e987ef335914c0a846e00ae4178 100644 (file)
@@ -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 {
index 9ba717c9ceb20dbc7217320b912cafccc7a1d188..2752fcc86db55f620dfe885357542f8a657dd800 100644 (file)
@@ -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")
 }