]> Cypherpunks repositories - gostls13.git/commitdiff
encoding/json: improve performance of Compact
authorPhil Pearl <philip.j.r.pearl@gmail.com>
Sun, 13 Oct 2019 12:01:58 +0000 (13:01 +0100)
committerDaniel Martí <mvdan@mvdan.cc>
Sun, 27 Oct 2019 16:02:29 +0000 (16:02 +0000)
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>

src/encoding/json/bench_test.go
src/encoding/json/indent.go
src/encoding/json/scanner.go

index f92d39f0c657aa395409024d6e3189e0f267f1d4..4a5fe7ec84e3fec9f1a168fe0465e4608c947c7e 100644 (file)
@@ -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)
+                       }
+               }
+       })
+}
index 06adfc12635aab10474ebc813f3d38a0a3b0a515..2924d3b49b944169569531c7702ccc96d028454a 100644 (file)
@@ -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
                }
index 88572245fc71001a03c7f08a4e06b24ae8324437..552bd703604bac146ecfc57760eb678331a0768d 100644 (file)
@@ -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