]> Cypherpunks repositories - gostls13.git/commitdiff
encoding/json: various minor decoder speed-ups
authorDaniel Martí <mvdan@mvdan.cc>
Sun, 8 Jul 2018 15:14:35 +0000 (16:14 +0100)
committerDaniel Martí <mvdan@mvdan.cc>
Tue, 21 Aug 2018 19:00:08 +0000 (19:00 +0000)
Reuse v.Type() and cachedTypeFields(t) when decoding maps and structs.

Always use the same data slices when in hot loops, to ensure that the
compiler generates good code. "for i < len(data) { use(d.data[i]) }"
makes it harder for the compiler.

Finally, do other minor clean-ups, such as deduplicating switch cases,
and using a switch instead of three chained ifs.

The decoder sees a noticeable speed-up, in particular when decoding
structs.

name           old time/op    new time/op    delta
CodeDecoder-4    29.8ms ± 1%    27.5ms ± 0%  -7.83%  (p=0.002 n=6+6)

name           old speed      new speed      delta
CodeDecoder-4  65.0MB/s ± 1%  70.6MB/s ± 0%  +8.49%  (p=0.002 n=6+6)

Updates #5683.

Change-Id: I9d751e22502221962da696e48996ffdeb777277d
Reviewed-on: https://go-review.googlesource.com/122468
Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
src/encoding/json/decode.go
src/encoding/json/stream.go

index 16da48617e9ae61697b2165a88feb5c36ec7397d..2e734fb39e03394f3c3405198b64ee788b794ca9 100644 (file)
@@ -332,13 +332,12 @@ func (d *decodeState) skip() {
 
 // scanNext processes the byte at d.data[d.off].
 func (d *decodeState) scanNext() {
-       s, data, i := &d.scan, d.data, d.off
-       if i < len(data) {
-               d.opcode = s.step(s, data[i])
-               d.off = i + 1
+       if d.off < len(d.data) {
+               d.opcode = d.scan.step(&d.scan, d.data[d.off])
+               d.off++
        } else {
-               d.opcode = s.eof()
-               d.off = len(data) + 1 // mark processed EOF with len+1
+               d.opcode = d.scan.eof()
+               d.off = len(d.data) + 1 // mark processed EOF with len+1
        }
 }
 
@@ -346,7 +345,7 @@ func (d *decodeState) scanNext() {
 // receives a scan code not equal to op.
 func (d *decodeState) scanWhile(op int) {
        s, data, i := &d.scan, d.data, d.off
-       for i < len(d.data) {
+       for i < len(data) {
                newOp := s.step(s, data[i])
                i++
                if newOp != op {
@@ -356,7 +355,7 @@ func (d *decodeState) scanWhile(op int) {
                }
        }
 
-       d.off = len(d.data) + 1 // mark processed EOF with len+1
+       d.off = len(data) + 1 // mark processed EOF with len+1
        d.opcode = d.scan.eof()
 }
 
@@ -413,11 +412,7 @@ func (d *decodeState) valueQuoted() (interface{}, error) {
        default:
                return nil, errPhase
 
-       case scanBeginArray:
-               d.skip()
-               d.scanNext()
-
-       case scanBeginObject:
+       case scanBeginArray, scanBeginObject:
                d.skip()
                d.scanNext()
 
@@ -629,6 +624,7 @@ func (d *decodeState) object(v reflect.Value) error {
                return nil
        }
        v = pv
+       t := v.Type()
 
        // Decoding into nil interface? Switch to non-reflect code.
        if v.Kind() == reflect.Interface && v.NumMethod() == 0 {
@@ -640,6 +636,8 @@ func (d *decodeState) object(v reflect.Value) error {
                return nil
        }
 
+       var fields []field
+
        // Check type of target:
        //   struct or
        //   map[T1]T2 where T1 is string, an integer type,
@@ -648,14 +646,13 @@ func (d *decodeState) object(v reflect.Value) error {
        case reflect.Map:
                // Map key must either have string kind, have an integer kind,
                // or be an encoding.TextUnmarshaler.
-               t := v.Type()
                switch t.Key().Kind() {
                case reflect.String,
                        reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
                        reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
                default:
                        if !reflect.PtrTo(t.Key()).Implements(textUnmarshalerType) {
-                               d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)})
+                               d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)})
                                d.skip()
                                return nil
                        }
@@ -664,9 +661,10 @@ func (d *decodeState) object(v reflect.Value) error {
                        v.Set(reflect.MakeMap(t))
                }
        case reflect.Struct:
+               fields = cachedTypeFields(t)
                // ok
        default:
-               d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)})
+               d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)})
                d.skip()
                return nil
        }
@@ -698,7 +696,7 @@ func (d *decodeState) object(v reflect.Value) error {
                destring := false // whether the value is wrapped in a string to be decoded first
 
                if v.Kind() == reflect.Map {
-                       elemType := v.Type().Elem()
+                       elemType := t.Elem()
                        if !mapElem.IsValid() {
                                mapElem = reflect.New(elemType).Elem()
                        } else {
@@ -707,7 +705,6 @@ func (d *decodeState) object(v reflect.Value) error {
                        subv = mapElem
                } else {
                        var f *field
-                       fields := cachedTypeFields(v.Type())
                        for i := range fields {
                                ff := &fields[i]
                                if bytes.Equal(ff.nameBytes, key) {
@@ -744,7 +741,7 @@ func (d *decodeState) object(v reflect.Value) error {
                                        subv = subv.Field(i)
                                }
                                d.errorContext.Field = f.name
-                               d.errorContext.Struct = v.Type()
+                               d.errorContext.Struct = t
                        } else if d.disallowUnknownFields {
                                d.saveError(fmt.Errorf("json: unknown field %q", key))
                        }
@@ -785,13 +782,13 @@ func (d *decodeState) object(v reflect.Value) error {
                // Write value back to map;
                // if using struct, subv points into struct already.
                if v.Kind() == reflect.Map {
-                       kt := v.Type().Key()
+                       kt := t.Key()
                        var kv reflect.Value
                        switch {
                        case kt.Kind() == reflect.String:
                                kv = reflect.ValueOf(key).Convert(kt)
                        case reflect.PtrTo(kt).Implements(textUnmarshalerType):
-                               kv = reflect.New(v.Type().Key())
+                               kv = reflect.New(kt)
                                if err := d.literalStore(item, kv, true); err != nil {
                                        return err
                                }
index 63aa0309555ef9a6bb1ccc95d30bba2172a86583..7d5137fbc716f289f273eac02bd7b39e44f332f7 100644 (file)
@@ -96,19 +96,19 @@ Input:
                // Look in the buffer for a new value.
                for i, c := range dec.buf[scanp:] {
                        dec.scan.bytes++
-                       v := dec.scan.step(&dec.scan, c)
-                       if v == scanEnd {
+                       switch dec.scan.step(&dec.scan, c) {
+                       case scanEnd:
                                scanp += i
                                break Input
-                       }
-                       // scanEnd is delayed one byte.
-                       // We might block trying to get that byte from src,
-                       // so instead invent a space byte.
-                       if (v == scanEndObject || v == scanEndArray) && dec.scan.step(&dec.scan, ' ') == scanEnd {
-                               scanp += i + 1
-                               break Input
-                       }
-                       if v == scanError {
+                       case scanEndObject, scanEndArray:
+                               // scanEnd is delayed one byte.
+                               // We might block trying to get that byte from src,
+                               // so instead invent a space byte.
+                               if stateEndValue(&dec.scan, ' ') == scanEnd {
+                                       scanp += i + 1
+                                       break Input
+                               }
+                       case scanError:
                                dec.err = dec.scan.err
                                return 0, dec.scan.err
                        }