internal/encoding/json: improve allocation of Value for JSON strings

Substitute interface Value.value field with per type field boo and str
instead.

name      old time/op    new time/op    delta
String-4     286ns ± 0%     254ns ± 0%   ~     (p=1.000 n=1+1)
Bool-4       209ns ± 0%     211ns ± 0%   ~     (p=1.000 n=1+1)

name      old alloc/op   new alloc/op   delta
String-4      192B ± 0%      176B ± 0%   ~     (p=1.000 n=1+1)
Bool-4       0.00B          0.00B        ~     (all equal)

name      old allocs/op  new allocs/op  delta
String-4      4.00 ± 0%      3.00 ± 0%   ~     (p=1.000 n=1+1)
Bool-4        0.00           0.00        ~     (all equal)

Change-Id: Ib0167d22e60d63c221c303b79c75b9e96d432fe7
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/170277
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
This commit is contained in:
Herbie Ong 2019-03-31 19:10:33 -07:00
parent 61968ce130
commit 670d808a6d
2 changed files with 75 additions and 25 deletions

View File

@ -11,7 +11,7 @@ import (
)
func BenchmarkFloat(b *testing.B) {
input := []byte("1.797693134862315708145274237317043567981e+308")
input := []byte(`1.797693134862315708145274237317043567981e+308`)
for i := 0; i < b.N; i++ {
dec := json.NewDecoder(input)
val, err := dec.Read()
@ -26,7 +26,7 @@ func BenchmarkFloat(b *testing.B) {
}
func BenchmarkInt(b *testing.B) {
input := []byte("922337203.6854775807e+10")
input := []byte(`922337203.6854775807e+10`)
for i := 0; i < b.N; i++ {
dec := json.NewDecoder(input)
val, err := dec.Read()
@ -39,3 +39,30 @@ func BenchmarkInt(b *testing.B) {
}
}
}
func BenchmarkString(b *testing.B) {
input := []byte(`"abcdefghijklmnopqrstuvwxyz0123456789\\n\\t"`)
for i := 0; i < b.N; i++ {
dec := json.NewDecoder(input)
val, err := dec.Read()
if err != nil {
b.Fatal(err)
}
_ = val.String()
}
}
func BenchmarkBool(b *testing.B) {
input := []byte(`true`)
for i := 0; i < b.N; i++ {
dec := json.NewDecoder(input)
val, err := dec.Read()
if err != nil {
b.Fatal(err)
}
_, err = val.Bool()
if err != nil {
b.Fatal(err)
}
}
}

View File

@ -163,7 +163,7 @@ func (d *Decoder) parseNext() (value Value, n int, err error) {
in := d.in
if len(in) == 0 {
return d.newValue(EOF, nil, nil), 0, nil
return d.newValue(nil, EOF), 0, nil
}
switch in[0] {
@ -174,11 +174,11 @@ func (d *Decoder) parseNext() (value Value, n int, err error) {
}
switch in[0] {
case 'n':
return d.newValue(Null, in[:n], nil), n, nil
return d.newValue(in[:n], Null), n, nil
case 't':
return d.newValue(Bool, in[:n], true), n, nil
return d.newBoolValue(in[:n], true), n, nil
case 'f':
return d.newValue(Bool, in[:n], false), n, nil
return d.newBoolValue(in[:n], false), n, nil
}
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
@ -186,7 +186,7 @@ func (d *Decoder) parseNext() (value Value, n int, err error) {
if !ok {
return Value{}, 0, d.newSyntaxError("invalid number %s", errRegexp.Find(in))
}
return d.newValue(Number, in[:n], nil), n, nil
return d.newValue(in[:n], Number), n, nil
case '"':
var nerr errors.NonFatal
@ -194,22 +194,22 @@ func (d *Decoder) parseNext() (value Value, n int, err error) {
if !nerr.Merge(err) {
return Value{}, 0, err
}
return d.newValue(String, in[:n], s), n, nerr.E
return d.newStringValue(in[:n], s), n, nerr.E
case '{':
return d.newValue(StartObject, in[:1], nil), 1, nil
return d.newValue(in[:1], StartObject), 1, nil
case '}':
return d.newValue(EndObject, in[:1], nil), 1, nil
return d.newValue(in[:1], EndObject), 1, nil
case '[':
return d.newValue(StartArray, in[:1], nil), 1, nil
return d.newValue(in[:1], StartArray), 1, nil
case ']':
return d.newValue(EndArray, in[:1], nil), 1, nil
return d.newValue(in[:1], EndArray), 1, nil
case ',':
return d.newValue(comma, in[:1], nil), 1, nil
return d.newValue(in[:1], comma), 1, nil
}
return Value{}, 0, d.newSyntaxError("invalid value %s", errRegexp.Find(in))
}
@ -288,30 +288,53 @@ func (d *Decoder) isValueNext() bool {
d.value.typ, start))
}
// newValue constructs a Value.
func (d *Decoder) newValue(typ Type, input []byte, value interface{}) Value {
// newValue constructs a Value for given Type.
func (d *Decoder) newValue(input []byte, typ Type) Value {
line, column := d.position()
return Value{
input: input,
line: line,
column: column,
typ: typ,
value: value,
}
}
// newBoolValue constructs a Value for a JSON boolean.
func (d *Decoder) newBoolValue(input []byte, b bool) Value {
line, column := d.position()
return Value{
input: input,
line: line,
column: column,
typ: Bool,
boo: b,
}
}
// newStringValue constructs a Value for a JSON string.
func (d *Decoder) newStringValue(input []byte, s string) Value {
line, column := d.position()
return Value{
input: input,
line: line,
column: column,
typ: String,
str: s,
}
}
// Value contains a JSON type and value parsed from calling Decoder.Read.
// For JSON boolean and string, it holds the converted value in boo and str
// fields respectively. For JSON number, input field holds a valid number which
// is converted only in Int or Float. Other JSON types do not require any
// additional data.
type Value struct {
input []byte
line int
column int
typ Type
// value will be set to the following Go type based on the type field:
// Bool => bool
// String => string
// Name => string
// It will be nil if none of the above.
value interface{}
boo bool
str string
}
func (v Value) newError(f string, x ...interface{}) error {
@ -334,7 +357,7 @@ func (v Value) Bool() (bool, error) {
if v.typ != Bool {
return false, v.newError("%s is not a bool", v.input)
}
return v.value.(bool), nil
return v.boo, nil
}
// String returns the string value for a JSON string token or the read value in
@ -343,7 +366,7 @@ func (v Value) String() string {
if v.typ != String {
return string(v.input)
}
return v.value.(string)
return v.str
}
// Name returns the object name if token is Name, else it will return an error.
@ -351,7 +374,7 @@ func (v Value) Name() (string, error) {
if v.typ != Name {
return "", v.newError("%s is not an object name", v.input)
}
return v.value.(string), nil
return v.str, nil
}
// Float returns the floating-point number if token is Number, else it will