Skip to content

Commit

Permalink
encoding/json: various minor decoder speed-ups
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
mvdan committed Aug 21, 2018
1 parent b103528 commit 792d11c
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 32 deletions.
39 changes: 18 additions & 21 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,21 +332,20 @@ 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
}
}

// scanWhile processes bytes in d.data[d.off:] until it
// 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 {
Expand All @@ -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()
}

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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 {
Expand All @@ -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,
Expand All @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -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))
}
Expand Down Expand Up @@ -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
}
Expand Down
22 changes: 11 additions & 11 deletions stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit 792d11c

Please sign in to comment.