diff --git a/.travis.yml b/.travis.yml index 449e67cd..5571ae5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: go go: - - 1.8.x + - 1.10.x - 1.x before_install: diff --git a/config.go b/config.go index 8c58fcba..0f2e1d1f 100644 --- a/config.go +++ b/config.go @@ -4,6 +4,7 @@ import ( "encoding/json" "io" "reflect" + "strings" "sync" "unsafe" @@ -79,6 +80,7 @@ type frozenConfig struct { extraExtensions []Extension streamPool *sync.Pool iteratorPool *sync.Pool + stringBuilderPool *sync.Pool caseSensitive bool } @@ -145,6 +147,11 @@ func (cfg Config) Froze() API { return NewIterator(api) }, } + api.stringBuilderPool = &sync.Pool{ + New: func() interface{} { + return &strings.Builder{} + }, + } api.initCache() encoderExtension := EncoderExtension{} decoderExtension := DecoderExtension{} diff --git a/iter.go b/iter.go index 95ae54fb..11bb40f7 100644 --- a/iter.go +++ b/iter.go @@ -3,6 +3,7 @@ package jsoniter import ( "encoding/json" "fmt" + "github.com/junchih/stringx" "io" ) @@ -72,6 +73,7 @@ type Iterator struct { cfg *frozenConfig reader io.Reader buf []byte + strf *stringx.Factory head int tail int captureStartedAt int @@ -86,6 +88,7 @@ func NewIterator(cfg API) *Iterator { cfg: cfg.(*frozenConfig), reader: nil, buf: nil, + strf: stringx.NewFactory(), head: 0, tail: 0, } @@ -97,6 +100,7 @@ func Parse(cfg API, reader io.Reader, bufSize int) *Iterator { cfg: cfg.(*frozenConfig), reader: reader, buf: make([]byte, bufSize), + strf: stringx.NewFactory(), head: 0, tail: 0, } @@ -108,6 +112,7 @@ func ParseBytes(cfg API, input []byte) *Iterator { cfg: cfg.(*frozenConfig), reader: nil, buf: input, + strf: stringx.NewFactory(), head: 0, tail: len(input), } diff --git a/iter_str.go b/iter_str.go index adc487ea..6581b326 100644 --- a/iter_str.go +++ b/iter_str.go @@ -2,6 +2,7 @@ package jsoniter import ( "fmt" + "strings" "unicode/utf16" ) @@ -12,7 +13,7 @@ func (iter *Iterator) ReadString() (ret string) { for i := iter.head; i < iter.tail; i++ { c := iter.buf[i] if c == '"' { - ret = string(iter.buf[iter.head:i]) + ret = iter.strf.NewString(iter.buf[iter.head:i]) iter.head = i + 1 return ret } else if c == '\\' { @@ -28,87 +29,93 @@ func (iter *Iterator) ReadString() (ret string) { iter.skipThreeBytes('u', 'l', 'l') return "" } - iter.ReportError("ReadString", `expects " or n, but found `+string([]byte{c})) + iter.ReportError("ReadString", `expects " or n, but found `+iter.strf.NewString([]byte{c})) return } -func (iter *Iterator) readStringSlowPath() (ret string) { - var str []byte +func (iter *Iterator) readStringSlowPath() string { + strb := iter.cfg.borrowStringBuilder() + defer iter.cfg.returnStringBuilder(strb) var c byte for iter.Error == nil { c = iter.readByte() if c == '"' { - return string(str) + return strb.String() } if c == '\\' { c = iter.readByte() - str = iter.readEscapedChar(c, str) + iter.readEscapedChar(c, strb) } else { - str = append(str, c) + strb.WriteByte(c) } } iter.ReportError("readStringSlowPath", "unexpected end of input") - return + return "" } -func (iter *Iterator) readEscapedChar(c byte, str []byte) []byte { +func (iter *Iterator) readEscapedChar(c byte, strb *strings.Builder) { switch c { case 'u': r := iter.readU4() if utf16.IsSurrogate(r) { c = iter.readByte() if iter.Error != nil { - return nil + strb.Reset() + return } if c != '\\' { iter.unreadByte() - str = appendRune(str, r) - return str + appendRune(strb, r) + return } c = iter.readByte() if iter.Error != nil { - return nil + strb.Reset() + return } if c != 'u' { - str = appendRune(str, r) - return iter.readEscapedChar(c, str) + appendRune(strb, r) + iter.readEscapedChar(c, strb) + return } r2 := iter.readU4() if iter.Error != nil { - return nil + strb.Reset() + return } combined := utf16.DecodeRune(r, r2) if combined == '\uFFFD' { - str = appendRune(str, r) - str = appendRune(str, r2) + appendRune(strb, r) + appendRune(strb, r2) } else { - str = appendRune(str, combined) + appendRune(strb, combined) } } else { - str = appendRune(str, r) + appendRune(strb, r) } case '"': - str = append(str, '"') + strb.WriteByte('"') case '\\': - str = append(str, '\\') + strb.WriteByte('\\') case '/': - str = append(str, '/') + strb.WriteByte('/') case 'b': - str = append(str, '\b') + strb.WriteByte('\b') case 'f': - str = append(str, '\f') + strb.WriteByte('\f') case 'n': - str = append(str, '\n') + strb.WriteByte('\n') case 'r': - str = append(str, '\r') + strb.WriteByte('\r') case 't': - str = append(str, '\t') + strb.WriteByte('\t') default: iter.ReportError("readEscapedChar", `invalid escape char after \`) - return nil + strb.Reset() + return } - return str + return } // ReadStringAsSlice read string from iterator without copying into string form. @@ -187,29 +194,25 @@ const ( runeError = '\uFFFD' // the "error" Rune or "Unicode replacement character" ) -func appendRune(p []byte, r rune) []byte { +func appendRune(p *strings.Builder, r rune) { // Negative values are erroneous. Making it unsigned addresses the problem. switch i := uint32(r); { case i <= rune1Max: - p = append(p, byte(r)) - return p + p.WriteByte(byte(r)) case i <= rune2Max: - p = append(p, t2|byte(r>>6)) - p = append(p, tx|byte(r)&maskx) - return p + p.WriteByte(t2 | byte(r>>6)) + p.WriteByte(tx | byte(r)&maskx) case i > maxRune, surrogateMin <= i && i <= surrogateMax: r = runeError fallthrough case i <= rune3Max: - p = append(p, t3|byte(r>>12)) - p = append(p, tx|byte(r>>6)&maskx) - p = append(p, tx|byte(r)&maskx) - return p + p.WriteByte(t3 | byte(r>>12)) + p.WriteByte(tx | byte(r>>6)&maskx) + p.WriteByte(tx | byte(r)&maskx) default: - p = append(p, t4|byte(r>>18)) - p = append(p, tx|byte(r>>12)&maskx) - p = append(p, tx|byte(r>>6)&maskx) - p = append(p, tx|byte(r)&maskx) - return p + p.WriteByte(t4 | byte(r>>18)) + p.WriteByte(tx | byte(r>>12)&maskx) + p.WriteByte(tx | byte(r>>6)&maskx) + p.WriteByte(tx | byte(r)&maskx) } } diff --git a/misc_tests/jsoniter_nested_test.go b/misc_tests/jsoniter_nested_test.go index 1e4994a1..0111db81 100644 --- a/misc_tests/jsoniter_nested_test.go +++ b/misc_tests/jsoniter_nested_test.go @@ -64,6 +64,20 @@ func Benchmark_jsoniter_nested(b *testing.B) { } } +func Benchmark_jsoniter_string(b *testing.B) { + dummy := func(str string) {} + for n := 0; n < b.N; n++ { + func() { + iter := jsoniter.ConfigDefault.BorrowIterator( + []byte(`"hello world!!! hello world!!! hello world!!!"`), + ) + defer jsoniter.ConfigDefault.ReturnIterator(iter) + hello := iter.ReadString() + dummy(hello) + }() + } +} + func readLevel1Hello(iter *jsoniter.Iterator) []Level2 { l2Array := make([]Level2, 0, 2) for iter.ReadArray() { diff --git a/pool.go b/pool.go index e2389b56..a25ae30a 100644 --- a/pool.go +++ b/pool.go @@ -2,6 +2,7 @@ package jsoniter import ( "io" + "strings" ) // IteratorPool a thread safe pool of iterators with same configuration @@ -40,3 +41,12 @@ func (cfg *frozenConfig) ReturnIterator(iter *Iterator) { iter.Attachment = nil cfg.iteratorPool.Put(iter) } + +func (cfg *frozenConfig) borrowStringBuilder() *strings.Builder { + return cfg.stringBuilderPool.Get().(*strings.Builder) +} + +func (cfg *frozenConfig) returnStringBuilder(builder *strings.Builder) { + builder.Reset() + cfg.stringBuilderPool.Put(builder) +}