mirror of
https://github.com/golang/go.git
synced 2025-11-10 13:41:05 +00:00
encoding/json: use append-like operations for encoding
As part of the effort to rely less on bytes.Buffer,
switch most operations to use more natural append-like operations.
This makes it easier to swap bytes.Buffer out with a buffer type
that only needs to support a minimal subset of operations.
As a simplification, we can remove the use of the scratch buffer
and use the available capacity of the buffer itself as the scratch.
Also, declare an inlineable mayAppendQuote function to conditionally
append a double-quote if necessary.
Performance:
name old time/op new time/op delta
CodeEncoder 405µs ± 2% 397µs ± 2% -1.94% (p=0.000 n=20+20)
CodeEncoderError 453µs ± 1% 444µs ± 4% -1.83% (p=0.000 n=19+19)
CodeMarshal 559µs ± 4% 548µs ± 2% -2.02% (p=0.001 n=19+17)
CodeMarshalError 724µs ± 3% 716µs ± 2% -1.13% (p=0.030 n=19+20)
EncodeMarshaler 24.9ns ±15% 22.9ns ± 5% ~ (p=0.086 n=20+17)
EncoderEncode 14.0ns ±27% 15.0ns ±20% ~ (p=0.365 n=20+20)
There is a slight performance gain across the board due to
the elimination of the scratch buffer. Appends are done directly
into the unused capacity of the underlying buffer,
avoiding an additional copy. See #53685
Updates #27735
Change-Id: Icf6d612a7f7a51ecd10097af092762dd1225d49e
Reviewed-on: https://go-review.googlesource.com/c/go/+/469558
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
Auto-Submit: Joseph Tsai <joetsai@digital-static.net>
Reviewed-by: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Run-TryBot: Joseph Tsai <joetsai@digital-static.net>
This commit is contained in:
parent
e2f4134025
commit
8e5f56a2e3
1 changed files with 38 additions and 63 deletions
|
|
@ -284,7 +284,6 @@ var hex = "0123456789abcdef"
|
|||
// An encodeState encodes JSON into a bytes.Buffer.
|
||||
type encodeState struct {
|
||||
bytes.Buffer // accumulated output
|
||||
scratch [64]byte
|
||||
|
||||
// Keep track of what pointers we've seen in the current recursive call
|
||||
// path, to avoid cycles that could lead to a stack overflow. Only do
|
||||
|
|
@ -345,7 +344,7 @@ func isEmptyValue(v reflect.Value) bool {
|
|||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
return v.Bool() == false
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
|
|
@ -541,39 +540,27 @@ func addrTextMarshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) {
|
|||
}
|
||||
|
||||
func boolEncoder(e *encodeState, v reflect.Value, opts encOpts) {
|
||||
if opts.quoted {
|
||||
e.WriteByte('"')
|
||||
}
|
||||
if v.Bool() {
|
||||
e.WriteString("true")
|
||||
} else {
|
||||
e.WriteString("false")
|
||||
}
|
||||
if opts.quoted {
|
||||
e.WriteByte('"')
|
||||
}
|
||||
b := e.AvailableBuffer()
|
||||
b = mayAppendQuote(b, opts.quoted)
|
||||
b = strconv.AppendBool(b, v.Bool())
|
||||
b = mayAppendQuote(b, opts.quoted)
|
||||
e.Write(b)
|
||||
}
|
||||
|
||||
func intEncoder(e *encodeState, v reflect.Value, opts encOpts) {
|
||||
b := strconv.AppendInt(e.scratch[:0], v.Int(), 10)
|
||||
if opts.quoted {
|
||||
e.WriteByte('"')
|
||||
}
|
||||
b := e.AvailableBuffer()
|
||||
b = mayAppendQuote(b, opts.quoted)
|
||||
b = strconv.AppendInt(b, v.Int(), 10)
|
||||
b = mayAppendQuote(b, opts.quoted)
|
||||
e.Write(b)
|
||||
if opts.quoted {
|
||||
e.WriteByte('"')
|
||||
}
|
||||
}
|
||||
|
||||
func uintEncoder(e *encodeState, v reflect.Value, opts encOpts) {
|
||||
b := strconv.AppendUint(e.scratch[:0], v.Uint(), 10)
|
||||
if opts.quoted {
|
||||
e.WriteByte('"')
|
||||
}
|
||||
b := e.AvailableBuffer()
|
||||
b = mayAppendQuote(b, opts.quoted)
|
||||
b = strconv.AppendUint(b, v.Uint(), 10)
|
||||
b = mayAppendQuote(b, opts.quoted)
|
||||
e.Write(b)
|
||||
if opts.quoted {
|
||||
e.WriteByte('"')
|
||||
}
|
||||
}
|
||||
|
||||
type floatEncoder int // number of bits
|
||||
|
|
@ -589,7 +576,8 @@ func (bits floatEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
|||
// See golang.org/issue/6384 and golang.org/issue/14135.
|
||||
// Like fmt %g, but the exponent cutoffs are different
|
||||
// and exponents themselves are not padded to two digits.
|
||||
b := e.scratch[:0]
|
||||
b := e.AvailableBuffer()
|
||||
b = mayAppendQuote(b, opts.quoted)
|
||||
abs := math.Abs(f)
|
||||
fmt := byte('f')
|
||||
// Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right.
|
||||
|
|
@ -607,14 +595,8 @@ func (bits floatEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
|||
b = b[:n-1]
|
||||
}
|
||||
}
|
||||
|
||||
if opts.quoted {
|
||||
e.WriteByte('"')
|
||||
}
|
||||
b = mayAppendQuote(b, opts.quoted)
|
||||
e.Write(b)
|
||||
if opts.quoted {
|
||||
e.WriteByte('"')
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -633,13 +615,11 @@ func stringEncoder(e *encodeState, v reflect.Value, opts encOpts) {
|
|||
if !isValidNumber(numStr) {
|
||||
e.error(fmt.Errorf("json: invalid number literal %q", numStr))
|
||||
}
|
||||
if opts.quoted {
|
||||
e.WriteByte('"')
|
||||
}
|
||||
e.WriteString(numStr)
|
||||
if opts.quoted {
|
||||
e.WriteByte('"')
|
||||
}
|
||||
b := e.AvailableBuffer()
|
||||
b = mayAppendQuote(b, opts.quoted)
|
||||
b = append(b, numStr...)
|
||||
b = mayAppendQuote(b, opts.quoted)
|
||||
e.Write(b)
|
||||
return
|
||||
}
|
||||
if opts.quoted {
|
||||
|
|
@ -839,28 +819,16 @@ func encodeByteSlice(e *encodeState, v reflect.Value, _ encOpts) {
|
|||
return
|
||||
}
|
||||
s := v.Bytes()
|
||||
e.WriteByte('"')
|
||||
encodedLen := base64.StdEncoding.EncodedLen(len(s))
|
||||
if encodedLen <= len(e.scratch) {
|
||||
// If the encoded bytes fit in e.scratch, avoid an extra
|
||||
// allocation and use the cheaper Encoding.Encode.
|
||||
dst := e.scratch[:encodedLen]
|
||||
base64.StdEncoding.Encode(dst, s)
|
||||
e.Write(dst)
|
||||
} else if encodedLen <= 1024 {
|
||||
// The encoded bytes are short enough to allocate for, and
|
||||
// Encoding.Encode is still cheaper.
|
||||
dst := make([]byte, encodedLen)
|
||||
base64.StdEncoding.Encode(dst, s)
|
||||
e.Write(dst)
|
||||
} else {
|
||||
// The encoded bytes are too long to cheaply allocate, and
|
||||
// Encoding.Encode is no longer noticeably cheaper.
|
||||
enc := base64.NewEncoder(base64.StdEncoding, e)
|
||||
enc.Write(s)
|
||||
enc.Close()
|
||||
}
|
||||
e.WriteByte('"')
|
||||
e.Grow(len(`"`) + encodedLen + len(`"`))
|
||||
|
||||
// TODO(https://go.dev/issue/53693): Use base64.Encoding.AppendEncode.
|
||||
b := e.AvailableBuffer()
|
||||
b = append(b, '"')
|
||||
base64.StdEncoding.Encode(b[len(b):][:encodedLen], s)
|
||||
b = b[:len(b)+encodedLen]
|
||||
b = append(b, '"')
|
||||
e.Write(b)
|
||||
}
|
||||
|
||||
// sliceEncoder just wraps an arrayEncoder, checking to make sure the value isn't nil.
|
||||
|
|
@ -1343,3 +1311,10 @@ func cachedTypeFields(t reflect.Type) structFields {
|
|||
f, _ := fieldCache.LoadOrStore(t, typeFields(t))
|
||||
return f.(structFields)
|
||||
}
|
||||
|
||||
func mayAppendQuote(b []byte, quoted bool) []byte {
|
||||
if quoted {
|
||||
b = append(b, '"')
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue