1
0
mirror of https://github.com/golang/go synced 2024-11-27 04:52:17 -07:00

encoding/json: Remove extra allocation in scanner.

When the scanner receives a non-whitespace character in stateEndTop,
it creates an error message and caches it to return on the next
transition. nextValue() uses the scanner to sub-scan for a value
inside a larger JSON structure. Since stateEndTop is triggered
*after* the ending byte, whatever character immediately follows the
sub-value gets pulled into the scanner's state machine as well.
Even though it is not used and doesn't cause an error, it does
cause the state machine to allocate an error that will never be used.

The fix is to probe the state machine with whitespace after
scanEndObject or scanEndArray to see if the next character would
result in a scanEnd state transition. If so, we can return right
away without processing the next character and avoid triggering
an allocation.

benchmark                       old ns/op     new ns/op     delta
BenchmarkCodeEncoder            17022194      16611336      -2.41%
BenchmarkCodeMarshal            18443250      18090144      -1.91%
BenchmarkCodeDecoder            61502053      61010936      -0.80%
BenchmarkCodeUnmarshal          61410829      60363605      -1.71%
BenchmarkCodeUnmarshalReuse     59124836      58361772      -1.29%
BenchmarkUnmarshalString        602           603           +0.17%
BenchmarkUnmarshalFloat64       535           537           +0.37%
BenchmarkUnmarshalInt64         482           482           +0.00%
BenchmarkIssue10335             1206          799           -33.75%
BenchmarkSkipValue              17605751      18355391      +4.26%
BenchmarkEncoderEncode          612           604           -1.31%

benchmark                  old MB/s     new MB/s     speedup
BenchmarkCodeEncoder       114.00       116.82       1.02x
BenchmarkCodeMarshal       105.21       107.27       1.02x
BenchmarkCodeDecoder       31.55        31.81        1.01x
BenchmarkCodeUnmarshal     31.60        32.15        1.02x
BenchmarkSkipValue         111.63       107.07       0.96x

benchmark                  old allocs     new allocs     delta
BenchmarkIssue10335        11             4              -63.64%
BenchmarkEncoderEncode     2              2              +0.00%

benchmark                  old bytes     new bytes     delta
BenchmarkIssue10335        376           272           -27.66%
BenchmarkEncoderEncode     40            40            +0.00%

Fixes #10335

Change-Id: I3d4f2b67f7a038adfb33ba48bb6b680f528baf18
Reviewed-on: https://go-review.googlesource.com/9074
Reviewed-by: Russ Cox <rsc@golang.org>
This commit is contained in:
Peter Waldschmidt 2015-04-18 05:30:30 -04:00 committed by Russ Cox
parent 4f4da07ea0
commit a13606e619
2 changed files with 19 additions and 1 deletions

View File

@ -187,3 +187,14 @@ func BenchmarkUnmarshalInt64(b *testing.B) {
}
}
}
func BenchmarkIssue10335(b *testing.B) {
b.ReportAllocs()
var s struct{}
j := []byte(`{"a":{ }}`)
for n := 0; n < b.N; n++ {
if err := Unmarshal(j, &s); err != nil {
b.Fatal(err)
}
}
}

View File

@ -38,8 +38,15 @@ func nextValue(data []byte, scan *scanner) (value, rest []byte, err error) {
scan.reset()
for i, c := range data {
v := scan.step(scan, int(c))
if v >= scanEnd {
if v >= scanEndObject {
switch v {
// probe the scanner with a space to determine whether we will
// get scanEnd on the next character. Otherwise, if the next character
// is not a space, scanEndTop allocates a needless error.
case scanEndObject, scanEndArray:
if scan.step(scan, ' ') == scanEnd {
return data[:i+1], data[i+1:], nil
}
case scanError:
return nil, nil, scan.err
case scanEnd: