From: Josh Bleecher Snyder Date: Wed, 18 Nov 2020 20:50:29 +0000 (-0800) Subject: encoding/json: reduce allocated space in Unmarshal X-Git-Tag: go1.17beta1~1398 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=04edf418d285df410745118aae756f9e0a9a00f5;p=gostls13.git encoding/json: reduce allocated space in Unmarshal The decodeState type is a large part of the allocated space during Unmarshal. The errorContext field is infrequently used, and only on error. Extract it into a pointer and allocate it separate when necessary. name old time/op new time/op delta UnmarshalString-8 115ns ± 5% 114ns ± 3% ~ (p=0.170 n=15+15) UnmarshalFloat64-8 113ns ± 2% 106ns ± 1% -6.42% (p=0.000 n=15+14) UnmarshalInt64-8 93.3ns ± 1% 86.9ns ± 4% -6.89% (p=0.000 n=14+15) name old alloc/op new alloc/op delta UnmarshalString-8 192B ± 0% 160B ± 0% -16.67% (p=0.000 n=15+15) UnmarshalFloat64-8 180B ± 0% 148B ± 0% -17.78% (p=0.000 n=15+15) UnmarshalInt64-8 176B ± 0% 144B ± 0% -18.18% (p=0.000 n=15+15) name old allocs/op new allocs/op delta UnmarshalString-8 2.00 ± 0% 2.00 ± 0% ~ (all equal) UnmarshalFloat64-8 2.00 ± 0% 2.00 ± 0% ~ (all equal) UnmarshalInt64-8 1.00 ± 0% 1.00 ± 0% ~ (all equal) Change-Id: I53f3f468e6c65f77a12e5138a2626455b197012d Reviewed-on: https://go-review.googlesource.com/c/go/+/271338 Trust: Josh Bleecher Snyder Trust: Daniel Martí Run-TryBot: Josh Bleecher Snyder TryBot-Result: Go Bot Reviewed-by: Daniel Martí --- diff --git a/src/encoding/json/decode.go b/src/encoding/json/decode.go index 86d8a69db7..a9917e72c7 100644 --- a/src/encoding/json/decode.go +++ b/src/encoding/json/decode.go @@ -200,16 +200,19 @@ func (n Number) Int64() (int64, error) { return strconv.ParseInt(string(n), 10, 64) } +// An errorContext provides context for type errors during decoding. +type errorContext struct { + Struct reflect.Type + FieldStack []string +} + // decodeState represents the state while decoding a JSON value. type decodeState struct { - data []byte - off int // next read offset in data - opcode int // last read result - scan scanner - errorContext struct { // provides context for type errors - Struct reflect.Type - FieldStack []string - } + data []byte + off int // next read offset in data + opcode int // last read result + scan scanner + errorContext *errorContext savedError error useNumber bool disallowUnknownFields bool @@ -229,10 +232,11 @@ func (d *decodeState) init(data []byte) *decodeState { d.data = data d.off = 0 d.savedError = nil - d.errorContext.Struct = nil - - // Reuse the allocated space for the FieldStack slice. - d.errorContext.FieldStack = d.errorContext.FieldStack[:0] + if d.errorContext != nil { + d.errorContext.Struct = nil + // Reuse the allocated space for the FieldStack slice. + d.errorContext.FieldStack = d.errorContext.FieldStack[:0] + } return d } @@ -246,12 +250,11 @@ func (d *decodeState) saveError(err error) { // addErrorContext returns a new error enhanced with information from d.errorContext func (d *decodeState) addErrorContext(err error) error { - if d.errorContext.Struct != nil || len(d.errorContext.FieldStack) > 0 { + if d.errorContext != nil && (d.errorContext.Struct != nil || len(d.errorContext.FieldStack) > 0) { switch err := err.(type) { case *UnmarshalTypeError: err.Struct = d.errorContext.Struct.Name() err.Field = strings.Join(d.errorContext.FieldStack, ".") - return err } } return err @@ -657,7 +660,10 @@ func (d *decodeState) object(v reflect.Value) error { } var mapElem reflect.Value - origErrorContext := d.errorContext + var origErrorContext errorContext + if d.errorContext != nil { + origErrorContext = *d.errorContext + } for { // Read opening " of string key or closing }. @@ -732,6 +738,9 @@ func (d *decodeState) object(v reflect.Value) error { } subv = subv.Field(i) } + if d.errorContext == nil { + d.errorContext = new(errorContext) + } d.errorContext.FieldStack = append(d.errorContext.FieldStack, f.name) d.errorContext.Struct = t } else if d.disallowUnknownFields { @@ -812,11 +821,13 @@ func (d *decodeState) object(v reflect.Value) error { if d.opcode == scanSkipSpace { d.scanWhile(scanSkipSpace) } - // Reset errorContext to its original state. - // Keep the same underlying array for FieldStack, to reuse the - // space and avoid unnecessary allocs. - d.errorContext.FieldStack = d.errorContext.FieldStack[:len(origErrorContext.FieldStack)] - d.errorContext.Struct = origErrorContext.Struct + if d.errorContext != nil { + // Reset errorContext to its original state. + // Keep the same underlying array for FieldStack, to reuse the + // space and avoid unnecessary allocs. + d.errorContext.FieldStack = d.errorContext.FieldStack[:len(origErrorContext.FieldStack)] + d.errorContext.Struct = origErrorContext.Struct + } if d.opcode == scanEndObject { break }