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>
diff --git a/internal/encoding/json/bench_test.go b/internal/encoding/json/bench_test.go
index 4db8af9..6c3f1d1 100644
--- a/internal/encoding/json/bench_test.go
+++ b/internal/encoding/json/bench_test.go
@@ -11,7 +11,7 @@
)
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 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 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)
+ }
+ }
+}
diff --git a/internal/encoding/json/decode.go b/internal/encoding/json/decode.go
index ddf3a68..a9262b8 100644
--- a/internal/encoding/json/decode.go
+++ b/internal/encoding/json/decode.go
@@ -163,7 +163,7 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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