1
0
mirror of https://github.com/golang/go synced 2024-10-01 11:38:34 -06:00

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 <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>
This commit is contained in:
korzhao 2023-08-02 17:39:09 +08:00 committed by Daniel Martí
parent dd38ade8d1
commit 4c5dac7202
2 changed files with 37 additions and 21 deletions

View File

@ -246,6 +246,22 @@ func BenchmarkMarshalBytesError(b *testing.B) {
b.Run("4096", benchMarshalBytesError(4096)) 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) { func BenchmarkCodeDecoder(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
if codeJSON == nil { if codeJSON == nil {

View File

@ -17,6 +17,7 @@ import (
"fmt" "fmt"
"math" "math"
"reflect" "reflect"
"slices"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -739,16 +740,20 @@ func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
e.WriteByte('{') e.WriteByte('{')
// Extract and sort the keys. // Extract and sort the keys.
sv := make([]reflectWithString, v.Len()) var (
mi := v.MapRange() sv = make([]reflectWithString, v.Len())
mi = v.MapRange()
err error
)
for i := 0; mi.Next(); i++ { for i := 0; mi.Next(); i++ {
sv[i].k = mi.Key() if sv[i].ks, err = resolveKeyName(mi.Key()); err != nil {
sv[i].v = mi.Value()
if err := sv[i].resolve(); err != nil {
e.error(fmt.Errorf("json: encoding error for type %q: %q", v.Type().String(), err.Error())) 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 { for i, kv := range sv {
if i > 0 { if i > 0 {
@ -927,31 +932,26 @@ func typeByIndex(t reflect.Type, index []int) reflect.Type {
} }
type reflectWithString struct { type reflectWithString struct {
k reflect.Value
v reflect.Value v reflect.Value
ks string ks string
} }
func (w *reflectWithString) resolve() error { func resolveKeyName(k reflect.Value) (string, error) {
if w.k.Kind() == reflect.String { if k.Kind() == reflect.String {
w.ks = w.k.String() return k.String(), nil
return nil
} }
if tm, ok := w.k.Interface().(encoding.TextMarshaler); ok { if tm, ok := k.Interface().(encoding.TextMarshaler); ok {
if w.k.Kind() == reflect.Pointer && w.k.IsNil() { if k.Kind() == reflect.Pointer && k.IsNil() {
return nil return "", nil
} }
buf, err := tm.MarshalText() buf, err := tm.MarshalText()
w.ks = string(buf) return string(buf), err
return err
} }
switch w.k.Kind() { switch k.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
w.ks = strconv.FormatInt(w.k.Int(), 10) return strconv.FormatInt(k.Int(), 10), nil
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
w.ks = strconv.FormatUint(w.k.Uint(), 10) return strconv.FormatUint(k.Uint(), 10), nil
return nil
} }
panic("unexpected map key type") panic("unexpected map key type")
} }