Skip to content

Commit 63acc48

Browse files
committed
encoding/gob: add custom decoder buffer for performance
As we did with encoding, provide a trivial byte reader for faster decoding. We can also reduce some of the copying by doing the allocation all at once using a slightly different interface from byte buffers. benchmark old ns/op new ns/op delta BenchmarkEndToEndPipe 13368 12902 -3.49% BenchmarkEndToEndByteBuffer 5969 5642 -5.48% BenchmarkEndToEndSliceByteBuffer 479485 470798 -1.81% BenchmarkEncodeComplex128Slice 92367 92201 -0.18% BenchmarkEncodeFloat64Slice 39990 38960 -2.58% BenchmarkEncodeInt32Slice 30510 27938 -8.43% BenchmarkEncodeStringSlice 33753 33365 -1.15% BenchmarkDecodeComplex128Slice 232278 196704 -15.32% BenchmarkDecodeFloat64Slice 150258 128191 -14.69% BenchmarkDecodeInt32Slice 133806 115748 -13.50% BenchmarkDecodeStringSlice 335117 300534 -10.32% LGTM=rsc R=rsc CC=golang-codereviews https://golang.org/cl/154360049
1 parent 8ba47e3 commit 63acc48

File tree

3 files changed

+78
-39
lines changed

3 files changed

+78
-39
lines changed

src/encoding/gob/codec_test.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ func testError(t *testing.T) {
5050
return
5151
}
5252

53+
func newDecBuffer(data []byte) *decBuffer {
54+
return &decBuffer{
55+
data: data,
56+
}
57+
}
58+
5359
// Test basic encode/decode routines for unsigned integers
5460
func TestUintCodec(t *testing.T) {
5561
defer testError(t)
@@ -65,7 +71,7 @@ func TestUintCodec(t *testing.T) {
6571
for u := uint64(0); ; u = (u + 1) * 7 {
6672
b.Reset()
6773
encState.encodeUint(u)
68-
decState := newDecodeState(bytes.NewBuffer(b.Bytes()))
74+
decState := newDecodeState(newDecBuffer(b.Bytes()))
6975
v := decState.decodeUint()
7076
if u != v {
7177
t.Errorf("Encode/Decode: sent %#x received %#x", u, v)
@@ -81,7 +87,7 @@ func verifyInt(i int64, t *testing.T) {
8187
var b = new(encBuffer)
8288
encState := newEncoderState(b)
8389
encState.encodeInt(i)
84-
decState := newDecodeState(bytes.NewBuffer(b.Bytes()))
90+
decState := newDecodeState(newDecBuffer(b.Bytes()))
8591
decState.buf = make([]byte, 8)
8692
j := decState.decodeInt()
8793
if i != j {
@@ -118,7 +124,7 @@ var complexResult = []byte{0x07, 0xFE, 0x31, 0x40, 0xFE, 0x33, 0x40}
118124
// The result of encoding "hello" with field number 7
119125
var bytesResult = []byte{0x07, 0x05, 'h', 'e', 'l', 'l', 'o'}
120126

121-
func newDecodeState(buf *bytes.Buffer) *decoderState {
127+
func newDecodeState(buf *decBuffer) *decoderState {
122128
d := new(decoderState)
123129
d.b = buf
124130
d.buf = make([]byte, uint64Size)
@@ -328,7 +334,7 @@ func execDec(typ string, instr *decInstr, state *decoderState, t *testing.T, val
328334
}
329335

330336
func newDecodeStateFromData(data []byte) *decoderState {
331-
b := bytes.NewBuffer(data)
337+
b := newDecBuffer(data)
332338
state := newDecodeState(b)
333339
state.fieldnum = -1
334340
return state

src/encoding/gob/decode.go

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package gob
88

99
import (
10-
"bytes"
1110
"encoding"
1211
"errors"
1312
"io"
@@ -29,15 +28,71 @@ type decoderState struct {
2928
dec *Decoder
3029
// The buffer is stored with an extra indirection because it may be replaced
3130
// if we load a type during decode (when reading an interface value).
32-
b *bytes.Buffer
31+
b *decBuffer
3332
fieldnum int // the last field number read.
3433
buf []byte
3534
next *decoderState // for free list
3635
}
3736

37+
// decBuffer is an extremely simple, fast implementation of a read-only byte buffer.
38+
// It is initialized by calling Size and then copying the data into the slice returned by Bytes().
39+
type decBuffer struct {
40+
data []byte
41+
offset int // Read offset.
42+
}
43+
44+
func (d *decBuffer) Read(p []byte) (int, error) {
45+
n := copy(p, d.data[d.offset:])
46+
if n == 0 && len(p) != 0 {
47+
return 0, io.EOF
48+
}
49+
d.offset += n
50+
return n, nil
51+
}
52+
53+
func (d *decBuffer) Drop(n int) {
54+
if n > d.Len() {
55+
panic("drop")
56+
}
57+
d.offset += n
58+
}
59+
60+
// Size grows the buffer to exactly n bytes, so d.Bytes() will
61+
// return a slice of length n. Existing data is first discarded.
62+
func (d *decBuffer) Size(n int) {
63+
d.Reset()
64+
if cap(d.data) < n {
65+
d.data = make([]byte, n)
66+
} else {
67+
d.data = d.data[0:n]
68+
}
69+
}
70+
71+
func (d *decBuffer) ReadByte() (byte, error) {
72+
if d.offset >= len(d.data) {
73+
return 0, io.EOF
74+
}
75+
c := d.data[d.offset]
76+
d.offset++
77+
return c, nil
78+
}
79+
80+
func (d *decBuffer) Len() int {
81+
return len(d.data) - d.offset
82+
}
83+
84+
func (d *decBuffer) Bytes() []byte {
85+
return d.data[d.offset:]
86+
}
87+
88+
func (d *decBuffer) Reset() {
89+
d.data = d.data[0:0]
90+
d.offset = 0
91+
}
92+
3893
// We pass the bytes.Buffer separately for easier testing of the infrastructure
3994
// without requiring a full Decoder.
40-
func (dec *Decoder) newDecoderState(buf *bytes.Buffer) *decoderState {
95+
func (dec *Decoder) newDecoderState(buf *decBuffer) *decoderState {
4196
d := dec.freeList
4297
if d == nil {
4398
d = new(decoderState)
@@ -633,7 +688,7 @@ func (dec *Decoder) ignoreInterface(state *decoderState) {
633688
error_(dec.err)
634689
}
635690
// At this point, the decoder buffer contains a delimited value. Just toss it.
636-
state.b.Next(int(state.decodeUint()))
691+
state.b.Drop(int(state.decodeUint()))
637692
}
638693

639694
// decodeGobDecoder decodes something implementing the GobDecoder interface.

src/encoding/gob/decoder.go

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package gob
66

77
import (
88
"bufio"
9-
"bytes"
109
"errors"
1110
"io"
1211
"reflect"
@@ -23,13 +22,12 @@ const tooBig = 1 << 30
2322
type Decoder struct {
2423
mutex sync.Mutex // each item must be received atomically
2524
r io.Reader // source of the data
26-
buf bytes.Buffer // buffer for more efficient i/o from r
25+
buf decBuffer // buffer for more efficient i/o from r
2726
wireType map[typeId]*wireType // map from remote ID to local description
2827
decoderCache map[reflect.Type]map[typeId]**decEngine // cache of compiled engines
2928
ignorerCache map[typeId]**decEngine // ditto for ignored objects
3029
freeList *decoderState // list of free decoderStates; avoids reallocation
3130
countBuf []byte // used for decoding integers while parsing messages
32-
tmp []byte // temporary storage for i/o; saves reallocating
3331
err error
3432
}
3533

@@ -90,37 +88,17 @@ func (dec *Decoder) recvMessage() bool {
9088

9189
// readMessage reads the next nbytes bytes from the input.
9290
func (dec *Decoder) readMessage(nbytes int) {
93-
// Allocate the dec.tmp buffer, up to 10KB.
94-
const maxBuf = 10 * 1024
95-
nTmp := nbytes
96-
if nTmp > maxBuf {
97-
nTmp = maxBuf
91+
if dec.buf.Len() != 0 {
92+
// The buffer should always be empty now.
93+
panic("non-empty decoder buffer")
9894
}
99-
if cap(dec.tmp) < nTmp {
100-
nAlloc := nTmp + 100 // A little extra for growth.
101-
if nAlloc > maxBuf {
102-
nAlloc = maxBuf
103-
}
104-
dec.tmp = make([]byte, nAlloc)
105-
}
106-
dec.tmp = dec.tmp[:nTmp]
107-
10895
// Read the data
109-
dec.buf.Grow(nbytes)
110-
for nbytes > 0 {
111-
if nbytes < nTmp {
112-
dec.tmp = dec.tmp[:nbytes]
113-
}
114-
var nRead int
115-
nRead, dec.err = io.ReadFull(dec.r, dec.tmp)
116-
if dec.err != nil {
117-
if dec.err == io.EOF {
118-
dec.err = io.ErrUnexpectedEOF
119-
}
120-
return
96+
dec.buf.Size(nbytes)
97+
_, dec.err = io.ReadFull(dec.r, dec.buf.Bytes())
98+
if dec.err != nil {
99+
if dec.err == io.EOF {
100+
dec.err = io.ErrUnexpectedEOF
121101
}
122-
dec.buf.Write(dec.tmp)
123-
nbytes -= nRead
124102
}
125103
}
126104

0 commit comments

Comments
 (0)