mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
encoding/json: various minor decoder speed-ups
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>
This commit is contained in:
parent
9a2a34e1c1
commit
6d4787aff2
2 changed files with 29 additions and 32 deletions
|
|
@ -332,13 +332,12 @@ func (d *decodeState) skip() {
|
||||||
|
|
||||||
// scanNext processes the byte at d.data[d.off].
|
// scanNext processes the byte at d.data[d.off].
|
||||||
func (d *decodeState) scanNext() {
|
func (d *decodeState) scanNext() {
|
||||||
s, data, i := &d.scan, d.data, d.off
|
if d.off < len(d.data) {
|
||||||
if i < len(data) {
|
d.opcode = d.scan.step(&d.scan, d.data[d.off])
|
||||||
d.opcode = s.step(s, data[i])
|
d.off++
|
||||||
d.off = i + 1
|
|
||||||
} else {
|
} else {
|
||||||
d.opcode = s.eof()
|
d.opcode = d.scan.eof()
|
||||||
d.off = len(data) + 1 // mark processed EOF with len+1
|
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.
|
// receives a scan code not equal to op.
|
||||||
func (d *decodeState) scanWhile(op int) {
|
func (d *decodeState) scanWhile(op int) {
|
||||||
s, data, i := &d.scan, d.data, d.off
|
s, data, i := &d.scan, d.data, d.off
|
||||||
for i < len(d.data) {
|
for i < len(data) {
|
||||||
newOp := s.step(s, data[i])
|
newOp := s.step(s, data[i])
|
||||||
i++
|
i++
|
||||||
if newOp != op {
|
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()
|
d.opcode = d.scan.eof()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -413,11 +412,7 @@ func (d *decodeState) valueQuoted() (interface{}, error) {
|
||||||
default:
|
default:
|
||||||
return nil, errPhase
|
return nil, errPhase
|
||||||
|
|
||||||
case scanBeginArray:
|
case scanBeginArray, scanBeginObject:
|
||||||
d.skip()
|
|
||||||
d.scanNext()
|
|
||||||
|
|
||||||
case scanBeginObject:
|
|
||||||
d.skip()
|
d.skip()
|
||||||
d.scanNext()
|
d.scanNext()
|
||||||
|
|
||||||
|
|
@ -629,6 +624,7 @@ func (d *decodeState) object(v reflect.Value) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
v = pv
|
v = pv
|
||||||
|
t := v.Type()
|
||||||
|
|
||||||
// Decoding into nil interface? Switch to non-reflect code.
|
// Decoding into nil interface? Switch to non-reflect code.
|
||||||
if v.Kind() == reflect.Interface && v.NumMethod() == 0 {
|
if v.Kind() == reflect.Interface && v.NumMethod() == 0 {
|
||||||
|
|
@ -640,6 +636,8 @@ func (d *decodeState) object(v reflect.Value) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fields []field
|
||||||
|
|
||||||
// Check type of target:
|
// Check type of target:
|
||||||
// struct or
|
// struct or
|
||||||
// map[T1]T2 where T1 is string, an integer type,
|
// 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:
|
case reflect.Map:
|
||||||
// Map key must either have string kind, have an integer kind,
|
// Map key must either have string kind, have an integer kind,
|
||||||
// or be an encoding.TextUnmarshaler.
|
// or be an encoding.TextUnmarshaler.
|
||||||
t := v.Type()
|
|
||||||
switch t.Key().Kind() {
|
switch t.Key().Kind() {
|
||||||
case reflect.String,
|
case reflect.String,
|
||||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
default:
|
default:
|
||||||
if !reflect.PtrTo(t.Key()).Implements(textUnmarshalerType) {
|
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()
|
d.skip()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -664,9 +661,10 @@ func (d *decodeState) object(v reflect.Value) error {
|
||||||
v.Set(reflect.MakeMap(t))
|
v.Set(reflect.MakeMap(t))
|
||||||
}
|
}
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
|
fields = cachedTypeFields(t)
|
||||||
// ok
|
// ok
|
||||||
default:
|
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()
|
d.skip()
|
||||||
return nil
|
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
|
destring := false // whether the value is wrapped in a string to be decoded first
|
||||||
|
|
||||||
if v.Kind() == reflect.Map {
|
if v.Kind() == reflect.Map {
|
||||||
elemType := v.Type().Elem()
|
elemType := t.Elem()
|
||||||
if !mapElem.IsValid() {
|
if !mapElem.IsValid() {
|
||||||
mapElem = reflect.New(elemType).Elem()
|
mapElem = reflect.New(elemType).Elem()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -707,7 +705,6 @@ func (d *decodeState) object(v reflect.Value) error {
|
||||||
subv = mapElem
|
subv = mapElem
|
||||||
} else {
|
} else {
|
||||||
var f *field
|
var f *field
|
||||||
fields := cachedTypeFields(v.Type())
|
|
||||||
for i := range fields {
|
for i := range fields {
|
||||||
ff := &fields[i]
|
ff := &fields[i]
|
||||||
if bytes.Equal(ff.nameBytes, key) {
|
if bytes.Equal(ff.nameBytes, key) {
|
||||||
|
|
@ -744,7 +741,7 @@ func (d *decodeState) object(v reflect.Value) error {
|
||||||
subv = subv.Field(i)
|
subv = subv.Field(i)
|
||||||
}
|
}
|
||||||
d.errorContext.Field = f.name
|
d.errorContext.Field = f.name
|
||||||
d.errorContext.Struct = v.Type()
|
d.errorContext.Struct = t
|
||||||
} else if d.disallowUnknownFields {
|
} else if d.disallowUnknownFields {
|
||||||
d.saveError(fmt.Errorf("json: unknown field %q", key))
|
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;
|
// Write value back to map;
|
||||||
// if using struct, subv points into struct already.
|
// if using struct, subv points into struct already.
|
||||||
if v.Kind() == reflect.Map {
|
if v.Kind() == reflect.Map {
|
||||||
kt := v.Type().Key()
|
kt := t.Key()
|
||||||
var kv reflect.Value
|
var kv reflect.Value
|
||||||
switch {
|
switch {
|
||||||
case kt.Kind() == reflect.String:
|
case kt.Kind() == reflect.String:
|
||||||
kv = reflect.ValueOf(key).Convert(kt)
|
kv = reflect.ValueOf(key).Convert(kt)
|
||||||
case reflect.PtrTo(kt).Implements(textUnmarshalerType):
|
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 {
|
if err := d.literalStore(item, kv, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,19 +96,19 @@ Input:
|
||||||
// Look in the buffer for a new value.
|
// Look in the buffer for a new value.
|
||||||
for i, c := range dec.buf[scanp:] {
|
for i, c := range dec.buf[scanp:] {
|
||||||
dec.scan.bytes++
|
dec.scan.bytes++
|
||||||
v := dec.scan.step(&dec.scan, c)
|
switch dec.scan.step(&dec.scan, c) {
|
||||||
if v == scanEnd {
|
case scanEnd:
|
||||||
scanp += i
|
scanp += i
|
||||||
break Input
|
break Input
|
||||||
}
|
case scanEndObject, scanEndArray:
|
||||||
// scanEnd is delayed one byte.
|
// scanEnd is delayed one byte.
|
||||||
// We might block trying to get that byte from src,
|
// We might block trying to get that byte from src,
|
||||||
// so instead invent a space byte.
|
// so instead invent a space byte.
|
||||||
if (v == scanEndObject || v == scanEndArray) && dec.scan.step(&dec.scan, ' ') == scanEnd {
|
if stateEndValue(&dec.scan, ' ') == scanEnd {
|
||||||
scanp += i + 1
|
scanp += i + 1
|
||||||
break Input
|
break Input
|
||||||
}
|
}
|
||||||
if v == scanError {
|
case scanError:
|
||||||
dec.err = dec.scan.err
|
dec.err = dec.scan.err
|
||||||
return 0, dec.scan.err
|
return 0, dec.scan.err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue