diff options
| author | Phil Pearl <philip.j.r.pearl@gmail.com> | 2019-10-13 13:01:58 +0100 |
|---|---|---|
| committer | Daniel Martí <mvdan@mvdan.cc> | 2019-10-27 16:02:29 +0000 |
| commit | 22d377077c01ced185f5f8d27f608e8c6dcb473c (patch) | |
| tree | 6e2a7c6470f2991ee2a54dded0dcb165bbf8217a /src/encoding/json | |
| parent | 31bfab4ac621e81100d7fc3bc8cf483c5d2d2fef (diff) | |
| download | go-22d377077c01ced185f5f8d27f608e8c6dcb473c.tar.xz | |
encoding/json: improve performance of Compact
This change improves performance of Compact by using a sync.Pool to allow re-use
of a scanner. This also has the side-effect of removing an allocation for each
field that implements Marshaler when marshalling JSON.
name old time/op new time/op delta
EncodeMarshaler-8 118ns ± 2% 104ns ± 1% -12.21% (p=0.001 n=7+7)
name old alloc/op new alloc/op delta
EncodeMarshaler-8 100B ± 0% 36B ± 0% -64.00% (p=0.000 n=8+8)
name old allocs/op new allocs/op delta
EncodeMarshaler-8 3.00 ± 0% 2.00 ± 0% -33.33% (p=0.000 n=8+8)
Change-Id: Ic70c61a0a6354823da5220f5aad04b94c054f233
Reviewed-on: https://go-review.googlesource.com/c/go/+/200864
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Diffstat (limited to 'src/encoding/json')
| -rw-r--r-- | src/encoding/json/bench_test.go | 19 | ||||
| -rw-r--r-- | src/encoding/json/indent.go | 16 | ||||
| -rw-r--r-- | src/encoding/json/scanner.go | 36 |
3 files changed, 60 insertions, 11 deletions
diff --git a/src/encoding/json/bench_test.go b/src/encoding/json/bench_test.go index f92d39f0c6..4a5fe7ec84 100644 --- a/src/encoding/json/bench_test.go +++ b/src/encoding/json/bench_test.go @@ -389,3 +389,22 @@ func BenchmarkTypeFieldsCache(b *testing.B) { }) } } + +func BenchmarkEncodeMarshaler(b *testing.B) { + b.ReportAllocs() + + m := struct { + A int + B RawMessage + }{} + + b.RunParallel(func(pb *testing.PB) { + enc := NewEncoder(ioutil.Discard) + + for pb.Next() { + if err := enc.Encode(&m); err != nil { + b.Fatal("Encode:", err) + } + } + }) +} diff --git a/src/encoding/json/indent.go b/src/encoding/json/indent.go index 06adfc1263..2924d3b49b 100644 --- a/src/encoding/json/indent.go +++ b/src/encoding/json/indent.go @@ -4,7 +4,9 @@ package json -import "bytes" +import ( + "bytes" +) // Compact appends to dst the JSON-encoded src with // insignificant space characters elided. @@ -14,8 +16,8 @@ func Compact(dst *bytes.Buffer, src []byte) error { func compact(dst *bytes.Buffer, src []byte, escape bool) error { origLen := dst.Len() - var scan scanner - scan.reset() + scan := newScanner() + defer freeScanner(scan) start := 0 for i, c := range src { if escape && (c == '<' || c == '>' || c == '&') { @@ -36,7 +38,7 @@ func compact(dst *bytes.Buffer, src []byte, escape bool) error { dst.WriteByte(hex[src[i+2]&0xF]) start = i + 3 } - v := scan.step(&scan, c) + v := scan.step(scan, c) if v >= scanSkipSpace { if v == scanError { break @@ -78,13 +80,13 @@ func newline(dst *bytes.Buffer, prefix, indent string, depth int) { // if src ends in a trailing newline, so will dst. func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error { origLen := dst.Len() - var scan scanner - scan.reset() + scan := newScanner() + defer freeScanner(scan) needIndent := false depth := 0 for _, c := range src { scan.bytes++ - v := scan.step(&scan, c) + v := scan.step(scan, c) if v == scanSkipSpace { continue } diff --git a/src/encoding/json/scanner.go b/src/encoding/json/scanner.go index 88572245fc..552bd70360 100644 --- a/src/encoding/json/scanner.go +++ b/src/encoding/json/scanner.go @@ -13,11 +13,16 @@ package json // This file starts with two simple examples using the scanner // before diving into the scanner itself. -import "strconv" +import ( + "strconv" + "sync" +) // Valid reports whether data is a valid JSON encoding. func Valid(data []byte) bool { - return checkValid(data, &scanner{}) == nil + scan := newScanner() + defer freeScanner(scan) + return checkValid(data, scan) == nil } // checkValid verifies that data is valid JSON-encoded data. @@ -45,7 +50,7 @@ type SyntaxError struct { func (e *SyntaxError) Error() string { return e.msg } // A scanner is a JSON scanning state machine. -// Callers call scan.reset() and then pass bytes in one at a time +// Callers call scan.reset and then pass bytes in one at a time // by calling scan.step(&scan, c) for each byte. // The return value, referred to as an opcode, tells the // caller about significant parsing events like beginning @@ -72,10 +77,33 @@ type scanner struct { // Error that happened, if any. err error - // total bytes consumed, updated by decoder.Decode + // total bytes consumed, updated by decoder.Decode (and deliberately + // not set to zero by scan.reset) bytes int64 } +var scannerPool = sync.Pool{ + New: func() interface{} { + return &scanner{} + }, +} + +func newScanner() *scanner { + scan := scannerPool.Get().(*scanner) + // scan.reset by design doesn't set bytes to zero + scan.bytes = 0 + scan.reset() + return scan +} + +func freeScanner(scan *scanner) { + // Avoid hanging on to too much memory in extreme cases. + if len(scan.parseState) > 1024 { + scan.parseState = nil + } + scannerPool.Put(scan) +} + // These values are returned by the state transition functions // assigned to scanner.state and the method scanner.eof. // They give details about the current state of the scan that |
