1
0
mirror of https://github.com/golang/go synced 2024-11-19 00:44:40 -07: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))
}
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 {

View 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")
}