Commit 63acc48f authored by Rob Pike's avatar Rob Pike

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
parent 8ba47e3d
...@@ -50,6 +50,12 @@ func testError(t *testing.T) { ...@@ -50,6 +50,12 @@ func testError(t *testing.T) {
return return
} }
func newDecBuffer(data []byte) *decBuffer {
return &decBuffer{
data: data,
}
}
// Test basic encode/decode routines for unsigned integers // Test basic encode/decode routines for unsigned integers
func TestUintCodec(t *testing.T) { func TestUintCodec(t *testing.T) {
defer testError(t) defer testError(t)
...@@ -65,7 +71,7 @@ func TestUintCodec(t *testing.T) { ...@@ -65,7 +71,7 @@ func TestUintCodec(t *testing.T) {
for u := uint64(0); ; u = (u + 1) * 7 { for u := uint64(0); ; u = (u + 1) * 7 {
b.Reset() b.Reset()
encState.encodeUint(u) encState.encodeUint(u)
decState := newDecodeState(bytes.NewBuffer(b.Bytes())) decState := newDecodeState(newDecBuffer(b.Bytes()))
v := decState.decodeUint() v := decState.decodeUint()
if u != v { if u != v {
t.Errorf("Encode/Decode: sent %#x received %#x", u, v) t.Errorf("Encode/Decode: sent %#x received %#x", u, v)
...@@ -81,7 +87,7 @@ func verifyInt(i int64, t *testing.T) { ...@@ -81,7 +87,7 @@ func verifyInt(i int64, t *testing.T) {
var b = new(encBuffer) var b = new(encBuffer)
encState := newEncoderState(b) encState := newEncoderState(b)
encState.encodeInt(i) encState.encodeInt(i)
decState := newDecodeState(bytes.NewBuffer(b.Bytes())) decState := newDecodeState(newDecBuffer(b.Bytes()))
decState.buf = make([]byte, 8) decState.buf = make([]byte, 8)
j := decState.decodeInt() j := decState.decodeInt()
if i != j { if i != j {
...@@ -118,7 +124,7 @@ var complexResult = []byte{0x07, 0xFE, 0x31, 0x40, 0xFE, 0x33, 0x40} ...@@ -118,7 +124,7 @@ var complexResult = []byte{0x07, 0xFE, 0x31, 0x40, 0xFE, 0x33, 0x40}
// The result of encoding "hello" with field number 7 // The result of encoding "hello" with field number 7
var bytesResult = []byte{0x07, 0x05, 'h', 'e', 'l', 'l', 'o'} var bytesResult = []byte{0x07, 0x05, 'h', 'e', 'l', 'l', 'o'}
func newDecodeState(buf *bytes.Buffer) *decoderState { func newDecodeState(buf *decBuffer) *decoderState {
d := new(decoderState) d := new(decoderState)
d.b = buf d.b = buf
d.buf = make([]byte, uint64Size) d.buf = make([]byte, uint64Size)
...@@ -328,7 +334,7 @@ func execDec(typ string, instr *decInstr, state *decoderState, t *testing.T, val ...@@ -328,7 +334,7 @@ func execDec(typ string, instr *decInstr, state *decoderState, t *testing.T, val
} }
func newDecodeStateFromData(data []byte) *decoderState { func newDecodeStateFromData(data []byte) *decoderState {
b := bytes.NewBuffer(data) b := newDecBuffer(data)
state := newDecodeState(b) state := newDecodeState(b)
state.fieldnum = -1 state.fieldnum = -1
return state return state
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
package gob package gob
import ( import (
"bytes"
"encoding" "encoding"
"errors" "errors"
"io" "io"
...@@ -29,15 +28,71 @@ type decoderState struct { ...@@ -29,15 +28,71 @@ type decoderState struct {
dec *Decoder dec *Decoder
// The buffer is stored with an extra indirection because it may be replaced // The buffer is stored with an extra indirection because it may be replaced
// if we load a type during decode (when reading an interface value). // if we load a type during decode (when reading an interface value).
b *bytes.Buffer b *decBuffer
fieldnum int // the last field number read. fieldnum int // the last field number read.
buf []byte buf []byte
next *decoderState // for free list next *decoderState // for free list
} }
// decBuffer is an extremely simple, fast implementation of a read-only byte buffer.
// It is initialized by calling Size and then copying the data into the slice returned by Bytes().
type decBuffer struct {
data []byte
offset int // Read offset.
}
func (d *decBuffer) Read(p []byte) (int, error) {
n := copy(p, d.data[d.offset:])
if n == 0 && len(p) != 0 {
return 0, io.EOF
}
d.offset += n
return n, nil
}
func (d *decBuffer) Drop(n int) {
if n > d.Len() {
panic("drop")
}
d.offset += n
}
// Size grows the buffer to exactly n bytes, so d.Bytes() will
// return a slice of length n. Existing data is first discarded.
func (d *decBuffer) Size(n int) {
d.Reset()
if cap(d.data) < n {
d.data = make([]byte, n)
} else {
d.data = d.data[0:n]
}
}
func (d *decBuffer) ReadByte() (byte, error) {
if d.offset >= len(d.data) {
return 0, io.EOF
}
c := d.data[d.offset]
d.offset++
return c, nil
}
func (d *decBuffer) Len() int {
return len(d.data) - d.offset
}
func (d *decBuffer) Bytes() []byte {
return d.data[d.offset:]
}
func (d *decBuffer) Reset() {
d.data = d.data[0:0]
d.offset = 0
}
// We pass the bytes.Buffer separately for easier testing of the infrastructure // We pass the bytes.Buffer separately for easier testing of the infrastructure
// without requiring a full Decoder. // without requiring a full Decoder.
func (dec *Decoder) newDecoderState(buf *bytes.Buffer) *decoderState { func (dec *Decoder) newDecoderState(buf *decBuffer) *decoderState {
d := dec.freeList d := dec.freeList
if d == nil { if d == nil {
d = new(decoderState) d = new(decoderState)
...@@ -633,7 +688,7 @@ func (dec *Decoder) ignoreInterface(state *decoderState) { ...@@ -633,7 +688,7 @@ func (dec *Decoder) ignoreInterface(state *decoderState) {
error_(dec.err) error_(dec.err)
} }
// At this point, the decoder buffer contains a delimited value. Just toss it. // At this point, the decoder buffer contains a delimited value. Just toss it.
state.b.Next(int(state.decodeUint())) state.b.Drop(int(state.decodeUint()))
} }
// decodeGobDecoder decodes something implementing the GobDecoder interface. // decodeGobDecoder decodes something implementing the GobDecoder interface.
......
...@@ -6,7 +6,6 @@ package gob ...@@ -6,7 +6,6 @@ package gob
import ( import (
"bufio" "bufio"
"bytes"
"errors" "errors"
"io" "io"
"reflect" "reflect"
...@@ -23,13 +22,12 @@ const tooBig = 1 << 30 ...@@ -23,13 +22,12 @@ const tooBig = 1 << 30
type Decoder struct { type Decoder struct {
mutex sync.Mutex // each item must be received atomically mutex sync.Mutex // each item must be received atomically
r io.Reader // source of the data r io.Reader // source of the data
buf bytes.Buffer // buffer for more efficient i/o from r buf decBuffer // buffer for more efficient i/o from r
wireType map[typeId]*wireType // map from remote ID to local description wireType map[typeId]*wireType // map from remote ID to local description
decoderCache map[reflect.Type]map[typeId]**decEngine // cache of compiled engines decoderCache map[reflect.Type]map[typeId]**decEngine // cache of compiled engines
ignorerCache map[typeId]**decEngine // ditto for ignored objects ignorerCache map[typeId]**decEngine // ditto for ignored objects
freeList *decoderState // list of free decoderStates; avoids reallocation freeList *decoderState // list of free decoderStates; avoids reallocation
countBuf []byte // used for decoding integers while parsing messages countBuf []byte // used for decoding integers while parsing messages
tmp []byte // temporary storage for i/o; saves reallocating
err error err error
} }
...@@ -90,37 +88,17 @@ func (dec *Decoder) recvMessage() bool { ...@@ -90,37 +88,17 @@ func (dec *Decoder) recvMessage() bool {
// readMessage reads the next nbytes bytes from the input. // readMessage reads the next nbytes bytes from the input.
func (dec *Decoder) readMessage(nbytes int) { func (dec *Decoder) readMessage(nbytes int) {
// Allocate the dec.tmp buffer, up to 10KB. if dec.buf.Len() != 0 {
const maxBuf = 10 * 1024 // The buffer should always be empty now.
nTmp := nbytes panic("non-empty decoder buffer")
if nTmp > maxBuf {
nTmp = maxBuf
} }
if cap(dec.tmp) < nTmp {
nAlloc := nTmp + 100 // A little extra for growth.
if nAlloc > maxBuf {
nAlloc = maxBuf
}
dec.tmp = make([]byte, nAlloc)
}
dec.tmp = dec.tmp[:nTmp]
// Read the data // Read the data
dec.buf.Grow(nbytes) dec.buf.Size(nbytes)
for nbytes > 0 { _, dec.err = io.ReadFull(dec.r, dec.buf.Bytes())
if nbytes < nTmp { if dec.err != nil {
dec.tmp = dec.tmp[:nbytes] if dec.err == io.EOF {
} dec.err = io.ErrUnexpectedEOF
var nRead int
nRead, dec.err = io.ReadFull(dec.r, dec.tmp)
if dec.err != nil {
if dec.err == io.EOF {
dec.err = io.ErrUnexpectedEOF
}
return
} }
dec.buf.Write(dec.tmp)
nbytes -= nRead
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment