commit 7ccc975d96d5fddcb7dc679fcc867b55c1bbf079
parent 4ddbe4cccf1017685edfbaf701f8248d743597e8
Author: francoispqt <francois@parquet.ninja>
Date: Sat, 5 May 2018 01:59:17 +0800
add buffr growing optimizations
Diffstat:
10 files changed, 107 insertions(+), 45 deletions(-)
diff --git a/benchmarks/encoder/encoder_bench_small_test.go b/benchmarks/encoder/encoder_bench_small_test.go
@@ -48,6 +48,25 @@ func BenchmarkGoJayEncodeSmallStruct(b *testing.B) {
}
}
+func BenchmarkGoJayEncodeSmallFunc(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ if _, err := gojay.MarshalObject(gojay.EncodeObjectFunc(func(enc *gojay.Encoder) {
+ enc.AddIntKey("st", 1)
+ enc.AddIntKey("sid", 1)
+ enc.AddStringKey("tt", "test")
+ enc.AddIntKey("gr", 1)
+ enc.AddStringKey("uuid", "test")
+ enc.AddStringKey("ip", "test")
+ enc.AddStringKey("ua", "test")
+ enc.AddIntKey("tz", 1)
+ enc.AddIntKey("v", 1)
+ })); err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
func TestGoJayEncodeSmallStruct(t *testing.T) {
if output, err := gojay.MarshalObject(benchmarks.NewSmallPayload()); err != nil {
t.Fatal(err)
diff --git a/encode_array.go b/encode_array.go
@@ -25,6 +25,7 @@ func (enc *Encoder) encodeArray(v MarshalerArray) ([]byte, error) {
// value must implement Marshaler
func (enc *Encoder) AddArray(v MarshalerArray) {
if v.IsNil() {
+ enc.grow(3)
r, ok := enc.getPreviousRune()
if ok && r != '[' {
enc.writeByte(',')
@@ -33,6 +34,7 @@ func (enc *Encoder) AddArray(v MarshalerArray) {
enc.writeByte(']')
return
}
+ enc.grow(100)
r, ok := enc.getPreviousRune()
if ok && r != '[' {
enc.writeByte(',')
@@ -48,6 +50,7 @@ func (enc *Encoder) AddArrayOmitEmpty(v MarshalerArray) {
if v.IsNil() {
return
}
+ enc.grow(4)
r, ok := enc.getPreviousRune()
if ok && r != '[' {
enc.writeByte(',')
@@ -61,22 +64,24 @@ func (enc *Encoder) AddArrayOmitEmpty(v MarshalerArray) {
// value must implement Marshaler
func (enc *Encoder) AddArrayKey(key string, v MarshalerArray) {
if v.IsNil() {
+ enc.grow(2 + len(key))
r, ok := enc.getPreviousRune()
if ok && r != '[' {
enc.writeByte(',')
}
enc.writeByte('"')
- enc.writeString(key)
+ enc.writeStringEscape(key)
enc.writeBytes(objKeyArr)
enc.writeByte(']')
return
}
+ enc.grow(5 + len(key))
r, ok := enc.getPreviousRune()
if ok && r != '[' && r != '{' {
enc.writeByte(',')
}
enc.writeByte('"')
- enc.writeString(key)
+ enc.writeStringEscape(key)
enc.writeBytes(objKeyArr)
v.MarshalArray(enc)
enc.writeByte(']')
@@ -88,12 +93,13 @@ func (enc *Encoder) AddArrayKeyOmitEmpty(key string, v MarshalerArray) {
if v.IsNil() {
return
}
+ enc.grow(5 + len(key))
r, ok := enc.getPreviousRune()
if ok && r != '[' && r != '{' {
enc.writeByte(',')
}
enc.writeByte('"')
- enc.writeString(key)
+ enc.writeStringEscape(key)
enc.writeBytes(objKeyArr)
v.MarshalArray(enc)
enc.writeByte(']')
diff --git a/encode_bool.go b/encode_bool.go
@@ -18,6 +18,7 @@ func (enc *Encoder) EncodeBool(v bool) error {
// encodeBool encodes a bool to JSON
func (enc *Encoder) encodeBool(v bool) ([]byte, error) {
+ enc.grow(5)
if v {
enc.writeString("true")
} else {
@@ -28,6 +29,7 @@ func (enc *Encoder) encodeBool(v bool) ([]byte, error) {
// AddBool adds a bool to be encoded, must be used inside a slice or array encoding (does not encode a key)
func (enc *Encoder) AddBool(v bool) {
+ enc.grow(5)
r, ok := enc.getPreviousRune()
if ok && r != '[' {
enc.writeByte(',')
@@ -44,6 +46,7 @@ func (enc *Encoder) AddBoolOmitEmpty(v bool) {
if v == false {
return
}
+ enc.grow(5)
r, ok := enc.getPreviousRune()
if ok && r != '[' {
enc.writeByte(',')
@@ -53,12 +56,13 @@ func (enc *Encoder) AddBoolOmitEmpty(v bool) {
// AddBoolKey adds a bool to be encoded, must be used inside an object as it will encode a key.
func (enc *Encoder) AddBoolKey(key string, value bool) {
+ enc.grow(5 + len(key))
r, ok := enc.getPreviousRune()
if ok && r != '{' && r != '[' {
enc.writeByte(',')
}
enc.writeByte('"')
- enc.writeString(key)
+ enc.writeStringEscape(key)
enc.writeBytes(objKey)
enc.buf = strconv.AppendBool(enc.buf, value)
}
@@ -69,12 +73,13 @@ func (enc *Encoder) AddBoolKeyOmitEmpty(key string, v bool) {
if v == false {
return
}
+ enc.grow(5 + len(key))
r, ok := enc.getPreviousRune()
if ok && r != '{' && r != '[' {
enc.writeByte(',')
}
enc.writeByte('"')
- enc.writeString(key)
+ enc.writeStringEscape(key)
enc.writeBytes(objKey)
enc.buf = strconv.AppendBool(enc.buf, v)
}
diff --git a/encode_builder.go b/encode_builder.go
@@ -1,15 +1,9 @@
-// Copyright 2017 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
package gojay
// grow grows b's capacity, if necessary, to guarantee space for
// another n bytes. After grow(n), at least n bytes can be written to b
// without another allocation. If n is negative, grow panics.
func (enc *Encoder) grow(n int) {
- if n < 0 {
- panic("Builder.grow: negative count")
- }
if cap(enc.buf)-len(enc.buf) < n {
Buf := make([]byte, len(enc.buf), 2*cap(enc.buf)+n)
copy(Buf, enc.buf)
diff --git a/encode_builder_test.go b/encode_builder_test.go
@@ -1,17 +1 @@
package gojay
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestEncoderBuilderError(t *testing.T) {
- enc := NewEncoder(nil)
- defer func() {
- err := recover()
- assert.NotNil(t, err, "err is not nil as we pass an invalid number to grow")
- }()
- enc.grow(-1)
- assert.True(t, false, "should not be called")
-}
diff --git a/encode_number.go b/encode_number.go
@@ -79,6 +79,7 @@ func (enc *Encoder) encodeFloat32(n float32) ([]byte, error) {
// AddInt adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key)
func (enc *Encoder) AddInt(v int) {
+ enc.grow(10)
r, ok := enc.getPreviousRune()
if ok && r != '[' {
enc.writeByte(',')
@@ -92,6 +93,7 @@ func (enc *Encoder) AddIntOmitEmpty(v int) {
if v == 0 {
return
}
+ enc.grow(10)
r, ok := enc.getPreviousRune()
if ok && r != '[' {
enc.writeByte(',')
@@ -101,6 +103,7 @@ func (enc *Encoder) AddIntOmitEmpty(v int) {
// AddFloat adds a float64 to be encoded, must be used inside a slice or array encoding (does not encode a key)
func (enc *Encoder) AddFloat(v float64) {
+ enc.grow(10)
r, ok := enc.getPreviousRune()
if ok && r != '[' {
enc.writeByte(',')
@@ -114,6 +117,7 @@ func (enc *Encoder) AddFloatOmitEmpty(v float64) {
if v == 0 {
return
}
+ enc.grow(10)
r, ok := enc.getPreviousRune()
if ok && r != '[' {
enc.writeByte(',')
@@ -136,6 +140,7 @@ func (enc *Encoder) AddFloat32OmitEmpty(v float32) {
if v == 0 {
return
}
+ enc.grow(10)
r, ok := enc.getPreviousRune()
if ok && r != '[' {
enc.writeByte(',')
@@ -145,12 +150,13 @@ func (enc *Encoder) AddFloat32OmitEmpty(v float32) {
// AddIntKey adds an int to be encoded, must be used inside an object as it will encode a key
func (enc *Encoder) AddIntKey(key string, v int) {
+ enc.grow(10 + len(key))
r, ok := enc.getPreviousRune()
if ok && r != '{' && r != '[' {
enc.writeByte(',')
}
enc.writeByte('"')
- enc.writeString(key)
+ enc.writeStringEscape(key)
enc.writeBytes(objKey)
enc.buf = strconv.AppendInt(enc.buf, int64(v), 10)
}
@@ -161,12 +167,13 @@ func (enc *Encoder) AddIntKeyOmitEmpty(key string, v int) {
if v == 0 {
return
}
+ enc.grow(10 + len(key))
r, ok := enc.getPreviousRune()
if ok && r != '{' && r != '[' {
enc.writeByte(',')
}
enc.writeByte('"')
- enc.writeString(key)
+ enc.writeStringEscape(key)
enc.writeBytes(objKey)
enc.buf = strconv.AppendInt(enc.buf, int64(v), 10)
}
@@ -177,8 +184,9 @@ func (enc *Encoder) AddFloatKey(key string, value float64) {
if ok && r != '{' && r != '[' {
enc.writeByte(',')
}
+ enc.grow(10)
enc.writeByte('"')
- enc.writeString(key)
+ enc.writeStringEscape(key)
enc.writeBytes(objKey)
enc.buf = strconv.AppendFloat(enc.buf, value, 'f', -1, 64)
}
@@ -189,24 +197,26 @@ func (enc *Encoder) AddFloatKeyOmitEmpty(key string, v float64) {
if v == 0 {
return
}
+ enc.grow(10 + len(key))
r, ok := enc.getPreviousRune()
if ok && r != '{' && r != '[' {
enc.writeByte(',')
}
enc.writeByte('"')
- enc.writeString(key)
+ enc.writeStringEscape(key)
enc.writeBytes(objKey)
enc.buf = strconv.AppendFloat(enc.buf, v, 'f', -1, 64)
}
// AddFloat32Key adds a float32 to be encoded, must be used inside an object as it will encode a key
func (enc *Encoder) AddFloat32Key(key string, v float32) {
+ enc.grow(10 + len(key))
r, ok := enc.getPreviousRune()
if ok && r != '{' && r != '[' {
enc.writeByte(',')
}
enc.writeByte('"')
- enc.writeString(key)
+ enc.writeStringEscape(key)
enc.writeByte('"')
enc.writeByte(':')
enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32)
@@ -218,12 +228,13 @@ func (enc *Encoder) AddFloat32KeyOmitEmpty(key string, v float32) {
if v == 0 {
return
}
+ enc.grow(10 + len(key))
r, ok := enc.getPreviousRune()
if ok && r != '{' && r != '[' {
enc.writeByte(',')
}
enc.writeByte('"')
- enc.writeString(key)
+ enc.writeStringEscape(key)
enc.writeBytes(objKey)
enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32)
}
diff --git a/encode_object.go b/encode_object.go
@@ -22,10 +22,13 @@ func (enc *Encoder) EncodeObject(v MarshalerObject) error {
}
return nil
}
+
func (enc *Encoder) encodeObject(v MarshalerObject) ([]byte, error) {
- enc.grow(200)
+ enc.grow(500)
enc.writeByte('{')
- v.MarshalObject(enc)
+ if !v.IsNil() {
+ v.MarshalObject(enc)
+ }
enc.writeByte('}')
return enc.buf, enc.err
}
@@ -34,6 +37,7 @@ func (enc *Encoder) encodeObject(v MarshalerObject) ([]byte, error) {
// value must implement MarshalerObject
func (enc *Encoder) AddObject(v MarshalerObject) {
if v.IsNil() {
+ enc.grow(2)
r, ok := enc.getPreviousRune()
if ok && r != '{' && r != '[' {
enc.writeByte(',')
@@ -42,6 +46,7 @@ func (enc *Encoder) AddObject(v MarshalerObject) {
enc.writeByte('}')
return
}
+ enc.grow(4)
r, ok := enc.getPreviousRune()
if ok && r != '[' {
enc.writeByte(',')
@@ -58,6 +63,7 @@ func (enc *Encoder) AddObjectOmitEmpty(v MarshalerObject) {
if v.IsNil() {
return
}
+ enc.grow(2)
r, ok := enc.getPreviousRune()
if ok && r != '[' {
enc.writeByte(',')
@@ -71,22 +77,24 @@ func (enc *Encoder) AddObjectOmitEmpty(v MarshalerObject) {
// value must implement MarshalerObject
func (enc *Encoder) AddObjectKey(key string, value MarshalerObject) {
if value.IsNil() {
+ enc.grow(2 + len(key))
r, ok := enc.getPreviousRune()
- if ok && r != '{' && r != '[' {
+ if ok && r != '{' {
enc.writeByte(',')
}
enc.writeByte('"')
- enc.writeString(key)
+ enc.writeStringEscape(key)
enc.writeBytes(objKeyObj)
enc.writeByte('}')
return
}
+ enc.grow(5 + len(key))
r, ok := enc.getPreviousRune()
- if ok && r != '{' && r != '[' {
+ if ok && r != '{' {
enc.writeByte(',')
}
enc.writeByte('"')
- enc.writeString(key)
+ enc.writeStringEscape(key)
enc.writeBytes(objKeyObj)
value.MarshalObject(enc)
enc.writeByte('}')
@@ -99,13 +107,33 @@ func (enc *Encoder) AddObjectKeyOmitEmpty(key string, value MarshalerObject) {
if value.IsNil() {
return
}
+ enc.grow(5 + len(key))
r, ok := enc.getPreviousRune()
- if ok && r != '{' && r != '[' {
+ if ok && r != '{' {
enc.writeByte(',')
}
enc.writeByte('"')
- enc.writeString(key)
+ enc.writeStringEscape(key)
enc.writeBytes(objKeyObj)
value.MarshalObject(enc)
enc.writeByte('}')
}
+
+// EncodeObjectFunc is a custom func type implementating MarshaleObject.
+// Use it to cast a func(*Encoder) to Marshal and object.
+//
+// enc := gojay.NewEncoder(io.Writer)
+// enc.EncoderObject(gojay.EncodeObjectFunc(func(enc *gojay.Encoder) {
+// enc.AddStringKey("hello", "world")
+// }))
+type EncodeObjectFunc func(*Encoder)
+
+// MarshalObject implements MarshalerObject.
+func (f EncodeObjectFunc) MarshalObject(enc *Encoder) {
+ f(enc)
+}
+
+// IsNil implements MarshalerObject.
+func (f EncodeObjectFunc) IsNil() bool {
+ return f == nil
+}
diff --git a/encode_object_test.go b/encode_object_test.go
@@ -298,6 +298,18 @@ func TestEncoderObjectMarshalAPI(t *testing.T) {
string(r),
"Result of marshalling is different as the one expected")
})
+ t.Run("marshal-object-func", func(t *testing.T) {
+ f := EncodeObjectFunc(func(enc *Encoder) {
+ enc.AddStringKey("test", "test")
+ })
+ r, err := Marshal(f)
+ assert.Nil(t, err, "Error should be nil")
+ assert.Equal(
+ t,
+ `{"test":"test"}`,
+ string(r),
+ "Result of marshalling is different as the one expected")
+ })
}
type TestObectOmitEmpty struct {
diff --git a/encode_string.go b/encode_string.go
@@ -24,6 +24,7 @@ func (enc *Encoder) encodeString(v string) ([]byte, error) {
// AddString adds a string to be encoded, must be used inside a slice or array encoding (does not encode a key)
func (enc *Encoder) AddString(v string) {
+ enc.grow(len(v) + 4)
r, ok := enc.getPreviousRune()
if ok && r != '[' {
enc.writeByte(',')
@@ -50,8 +51,9 @@ func (enc *Encoder) AddStringOmitEmpty(v string) {
// AddStringKey adds a string to be encoded, must be used inside an object as it will encode a key
func (enc *Encoder) AddStringKey(key, v string) {
+ enc.grow(len(key) + len(v) + 5)
r, ok := enc.getPreviousRune()
- if ok && r != '{' && r != '[' {
+ if ok && r != '{' {
enc.writeByte(',')
}
enc.writeByte('"')
@@ -67,8 +69,9 @@ func (enc *Encoder) AddStringKeyOmitEmpty(key, v string) {
if v == "" {
return
}
+ enc.grow(len(key) + len(v) + 5)
r, ok := enc.getPreviousRune()
- if ok && r != '{' && r != '[' {
+ if ok && r != '{' {
enc.writeByte(',')
}
enc.writeByte('"')
diff --git a/encode_string_test.go b/encode_string_test.go
@@ -69,14 +69,14 @@ func TestEncoderStringEncodeAPI(t *testing.T) {
"Result of marshalling is different as the one expected")
})
t.Run("escaped-sequence3", func(t *testing.T) {
- str := "hello \r world"
+ str := "hello \r world 𝄞"
builder := &strings.Builder{}
enc := NewEncoder(builder)
err := enc.EncodeString(str)
assert.Nil(t, err, "Error should be nil")
assert.Equal(
t,
- `"hello \r world"`,
+ `"hello \r world 𝄞"`,
builder.String(),
"Result of marshalling is different as the one expected")
})