mirror of
https://github.com/golang/go
synced 2024-11-12 08:20:22 -07:00
encoding/json: remove alloc when encoding short byte slices
If the encoded bytes fit in the bootstrap array encodeState.scratch, use that instead of allocating a new byte slice. Also tweaked the Encoding vs Encoder heuristic to use the length of the encoded bytes, not the length of the input bytes. Encoding is used for allocations of up to 1024 bytes, as we measured 2048 to be the point where it no longer provides a noticeable advantage. Also added some benchmarks. Only the first case changes in behavior. name old time/op new time/op delta MarshalBytes/32-4 420ns ± 1% 383ns ± 1% -8.69% (p=0.002 n=6+6) MarshalBytes/256-4 913ns ± 1% 915ns ± 0% ~ (p=0.580 n=5+6) MarshalBytes/4096-4 7.72µs ± 0% 7.74µs ± 0% ~ (p=0.340 n=5+6) name old alloc/op new alloc/op delta MarshalBytes/32-4 112B ± 0% 64B ± 0% -42.86% (p=0.002 n=6+6) MarshalBytes/256-4 736B ± 0% 736B ± 0% ~ (all equal) MarshalBytes/4096-4 7.30kB ± 0% 7.30kB ± 0% ~ (all equal) name old allocs/op new allocs/op delta MarshalBytes/32-4 2.00 ± 0% 1.00 ± 0% -50.00% (p=0.002 n=6+6) MarshalBytes/256-4 2.00 ± 0% 2.00 ± 0% ~ (all equal) MarshalBytes/4096-4 2.00 ± 0% 2.00 ± 0% ~ (all equal) Updates #5683. Change-Id: I5fa55c27bd7728338d770ae7c0756885ba9a5724 Reviewed-on: https://go-review.googlesource.com/122462 Run-TryBot: Daniel Martí <mvdan@mvdan.cc> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
2d3599e57d
commit
30d3ebe367
@ -114,6 +114,34 @@ func BenchmarkCodeMarshal(b *testing.B) {
|
||||
b.SetBytes(int64(len(codeJSON)))
|
||||
}
|
||||
|
||||
func benchMarshalBytes(n int) func(*testing.B) {
|
||||
sample := []byte("hello world")
|
||||
// Use a struct pointer, to avoid an allocation when passing it as an
|
||||
// interface parameter to Marshal.
|
||||
v := &struct {
|
||||
Bytes []byte
|
||||
}{
|
||||
bytes.Repeat(sample, (n/len(sample))+1)[:n],
|
||||
}
|
||||
return func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := Marshal(v); err != nil {
|
||||
b.Fatal("Marshal:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalBytes(b *testing.B) {
|
||||
// 32 fits within encodeState.scratch.
|
||||
b.Run("32", benchMarshalBytes(32))
|
||||
// 256 doesn't fit in encodeState.scratch, but is small enough to
|
||||
// allocate and avoid the slower base64.NewEncoder.
|
||||
b.Run("256", benchMarshalBytes(256))
|
||||
// 4096 is large enough that we want to avoid allocating for it.
|
||||
b.Run("4096", benchMarshalBytes(4096))
|
||||
}
|
||||
|
||||
func BenchmarkCodeDecoder(b *testing.B) {
|
||||
if codeJSON == nil {
|
||||
b.StopTimer()
|
||||
|
@ -718,14 +718,22 @@ func encodeByteSlice(e *encodeState, v reflect.Value, _ encOpts) {
|
||||
}
|
||||
s := v.Bytes()
|
||||
e.WriteByte('"')
|
||||
if len(s) < 1024 {
|
||||
// for small buffers, using Encode directly is much faster.
|
||||
dst := make([]byte, base64.StdEncoding.EncodedLen(len(s)))
|
||||
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 {
|
||||
// for large buffers, avoid unnecessary extra temporary
|
||||
// buffer space.
|
||||
// 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()
|
||||
|
Loading…
Reference in New Issue
Block a user