mirror of
https://github.com/golang/go
synced 2024-11-17 21:14:44 -07:00
encoding/json: index names for the struct decoder
In the common case, structs have a handful of fields and most inputs match struct field names exactly. The previous code would do a linear search over the fields, stopping at the first exact match, and otherwise using the first case insensitive match. This is unfortunate, because it means that for the common case, we'd do a linear search with bytes.Equal. Even for structs with only two or three fields, that is pretty wasteful. Worse even, up until the exact match was found via the linear search, all previous fields would run their equalFold functions, which aren't cheap even in the simple case. Instead, cache a map along with the field list that indexes the fields by their name. This way, a case sensitive field search doesn't involve a linear search, nor does it involve any equalFold func calls. This patch should also slightly speed up cases where there's a case insensitive match but not a case sensitive one, as then we'd avoid calling bytes.Equal on all the fields. Though that's not a common case, and there are no benchmarks for it. name old time/op new time/op delta CodeDecoder-8 11.0ms ± 0% 10.6ms ± 1% -4.42% (p=0.000 n=9+10) name old speed new speed delta CodeDecoder-8 176MB/s ± 0% 184MB/s ± 1% +4.62% (p=0.000 n=9+10) name old alloc/op new alloc/op delta CodeDecoder-8 2.28MB ± 0% 2.28MB ± 0% ~ (p=0.725 n=10+10) name old allocs/op new allocs/op delta CodeDecoder-8 76.9k ± 0% 76.9k ± 0% ~ (all equal) Updates #28923. Change-Id: I9929c1f06c76505e5b96914199315dbdaae5dc76 Reviewed-on: https://go-review.googlesource.com/c/go/+/172918 Run-TryBot: Daniel Martí <mvdan@mvdan.cc> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
980a57a84b
commit
7e08c7f43d
@ -8,7 +8,6 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
@ -691,7 +690,7 @@ func (d *decodeState) object(v reflect.Value) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var fields []field
|
||||
var fields structFields
|
||||
|
||||
// Check type of target:
|
||||
// struct or
|
||||
@ -761,14 +760,18 @@ func (d *decodeState) object(v reflect.Value) error {
|
||||
subv = mapElem
|
||||
} else {
|
||||
var f *field
|
||||
for i := range fields {
|
||||
ff := &fields[i]
|
||||
if bytes.Equal(ff.nameBytes, key) {
|
||||
if i, ok := fields.nameIndex[string(key)]; ok {
|
||||
// Found an exact name match.
|
||||
f = &fields.list[i]
|
||||
} else {
|
||||
// Fall back to the expensive case-insensitive
|
||||
// linear search.
|
||||
for i := range fields.list {
|
||||
ff := &fields.list[i]
|
||||
if ff.equalFold(ff.nameBytes, key) {
|
||||
f = ff
|
||||
break
|
||||
}
|
||||
if f == nil && ff.equalFold(ff.nameBytes, key) {
|
||||
f = ff
|
||||
}
|
||||
}
|
||||
if f != nil {
|
||||
|
@ -621,14 +621,19 @@ func unsupportedTypeEncoder(e *encodeState, v reflect.Value, _ encOpts) {
|
||||
}
|
||||
|
||||
type structEncoder struct {
|
||||
fields []field
|
||||
fields structFields
|
||||
}
|
||||
|
||||
type structFields struct {
|
||||
list []field
|
||||
nameIndex map[string]int
|
||||
}
|
||||
|
||||
func (se structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
||||
next := byte('{')
|
||||
FieldLoop:
|
||||
for i := range se.fields {
|
||||
f := &se.fields[i]
|
||||
for i := range se.fields.list {
|
||||
f := &se.fields.list[i]
|
||||
|
||||
// Find the nested struct field by following f.index.
|
||||
fv := v
|
||||
@ -1063,7 +1068,7 @@ func (x byIndex) Less(i, j int) bool {
|
||||
// typeFields returns a list of fields that JSON should recognize for the given type.
|
||||
// The algorithm is breadth-first search over the set of structs to include - the top struct
|
||||
// and then any reachable anonymous structs.
|
||||
func typeFields(t reflect.Type) []field {
|
||||
func typeFields(t reflect.Type) structFields {
|
||||
// Anonymous fields to explore at the current level and the next.
|
||||
current := []field{}
|
||||
next := []field{{typ: t}}
|
||||
@ -1237,7 +1242,11 @@ func typeFields(t reflect.Type) []field {
|
||||
f := &fields[i]
|
||||
f.encoder = typeEncoder(typeByIndex(t, f.index))
|
||||
}
|
||||
return fields
|
||||
nameIndex := make(map[string]int, len(fields))
|
||||
for i, field := range fields {
|
||||
nameIndex[field.name] = i
|
||||
}
|
||||
return structFields{fields, nameIndex}
|
||||
}
|
||||
|
||||
// dominantField looks through the fields, all of which are known to
|
||||
@ -1256,13 +1265,13 @@ func dominantField(fields []field) (field, bool) {
|
||||
return fields[0], true
|
||||
}
|
||||
|
||||
var fieldCache sync.Map // map[reflect.Type][]field
|
||||
var fieldCache sync.Map // map[reflect.Type]structFields
|
||||
|
||||
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
|
||||
func cachedTypeFields(t reflect.Type) []field {
|
||||
func cachedTypeFields(t reflect.Type) structFields {
|
||||
if f, ok := fieldCache.Load(t); ok {
|
||||
return f.([]field)
|
||||
return f.(structFields)
|
||||
}
|
||||
f, _ := fieldCache.LoadOrStore(t, typeFields(t))
|
||||
return f.([]field)
|
||||
return f.(structFields)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user