]> Cypherpunks repositories - gostls13.git/commitdiff
encoding/json: reduce allocated space in Unmarshal
authorJosh Bleecher Snyder <josharian@gmail.com>
Wed, 18 Nov 2020 20:50:29 +0000 (12:50 -0800)
committerJosh Bleecher Snyder <josharian@gmail.com>
Wed, 24 Feb 2021 14:12:16 +0000 (14:12 +0000)
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 <josharian@gmail.com>
Trust: Daniel Martí <mvdan@mvdan.cc>
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
src/encoding/json/decode.go

index 86d8a69db7e6d76aa40ca9d4b307ea2f43e909b7..a9917e72c761cd408021d37b712620d14ac2a84f 100644 (file)
@@ -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
                }