Skip to content

Commit 04edf41

Browse files
committed
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 <[email protected]> Trust: Daniel Martí <[email protected]> Run-TryBot: Josh Bleecher Snyder <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Daniel Martí <[email protected]>
1 parent e496120 commit 04edf41

File tree

1 file changed

+31
-20
lines changed

1 file changed

+31
-20
lines changed

src/encoding/json/decode.go

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -200,16 +200,19 @@ func (n Number) Int64() (int64, error) {
200200
return strconv.ParseInt(string(n), 10, 64)
201201
}
202202

203+
// An errorContext provides context for type errors during decoding.
204+
type errorContext struct {
205+
Struct reflect.Type
206+
FieldStack []string
207+
}
208+
203209
// decodeState represents the state while decoding a JSON value.
204210
type decodeState struct {
205-
data []byte
206-
off int // next read offset in data
207-
opcode int // last read result
208-
scan scanner
209-
errorContext struct { // provides context for type errors
210-
Struct reflect.Type
211-
FieldStack []string
212-
}
211+
data []byte
212+
off int // next read offset in data
213+
opcode int // last read result
214+
scan scanner
215+
errorContext *errorContext
213216
savedError error
214217
useNumber bool
215218
disallowUnknownFields bool
@@ -229,10 +232,11 @@ func (d *decodeState) init(data []byte) *decodeState {
229232
d.data = data
230233
d.off = 0
231234
d.savedError = nil
232-
d.errorContext.Struct = nil
233-
234-
// Reuse the allocated space for the FieldStack slice.
235-
d.errorContext.FieldStack = d.errorContext.FieldStack[:0]
235+
if d.errorContext != nil {
236+
d.errorContext.Struct = nil
237+
// Reuse the allocated space for the FieldStack slice.
238+
d.errorContext.FieldStack = d.errorContext.FieldStack[:0]
239+
}
236240
return d
237241
}
238242

@@ -246,12 +250,11 @@ func (d *decodeState) saveError(err error) {
246250

247251
// addErrorContext returns a new error enhanced with information from d.errorContext
248252
func (d *decodeState) addErrorContext(err error) error {
249-
if d.errorContext.Struct != nil || len(d.errorContext.FieldStack) > 0 {
253+
if d.errorContext != nil && (d.errorContext.Struct != nil || len(d.errorContext.FieldStack) > 0) {
250254
switch err := err.(type) {
251255
case *UnmarshalTypeError:
252256
err.Struct = d.errorContext.Struct.Name()
253257
err.Field = strings.Join(d.errorContext.FieldStack, ".")
254-
return err
255258
}
256259
}
257260
return err
@@ -657,7 +660,10 @@ func (d *decodeState) object(v reflect.Value) error {
657660
}
658661

659662
var mapElem reflect.Value
660-
origErrorContext := d.errorContext
663+
var origErrorContext errorContext
664+
if d.errorContext != nil {
665+
origErrorContext = *d.errorContext
666+
}
661667

662668
for {
663669
// Read opening " of string key or closing }.
@@ -732,6 +738,9 @@ func (d *decodeState) object(v reflect.Value) error {
732738
}
733739
subv = subv.Field(i)
734740
}
741+
if d.errorContext == nil {
742+
d.errorContext = new(errorContext)
743+
}
735744
d.errorContext.FieldStack = append(d.errorContext.FieldStack, f.name)
736745
d.errorContext.Struct = t
737746
} else if d.disallowUnknownFields {
@@ -812,11 +821,13 @@ func (d *decodeState) object(v reflect.Value) error {
812821
if d.opcode == scanSkipSpace {
813822
d.scanWhile(scanSkipSpace)
814823
}
815-
// Reset errorContext to its original state.
816-
// Keep the same underlying array for FieldStack, to reuse the
817-
// space and avoid unnecessary allocs.
818-
d.errorContext.FieldStack = d.errorContext.FieldStack[:len(origErrorContext.FieldStack)]
819-
d.errorContext.Struct = origErrorContext.Struct
824+
if d.errorContext != nil {
825+
// Reset errorContext to its original state.
826+
// Keep the same underlying array for FieldStack, to reuse the
827+
// space and avoid unnecessary allocs.
828+
d.errorContext.FieldStack = d.errorContext.FieldStack[:len(origErrorContext.FieldStack)]
829+
d.errorContext.Struct = origErrorContext.Struct
830+
}
820831
if d.opcode == scanEndObject {
821832
break
822833
}

0 commit comments

Comments
 (0)