io: add type-specific read/write methods
This seriously improves the serialization/deserialization performance for several reasons: * no time spent in `binary` reflection * no memory allocations being made on every read/write * uses fast ReadBytes everywhere it's appropriate It also makes Fixed8 Serializable just for convenience.
This commit is contained in:
parent
89d7f6d26e
commit
54d888ba70
43 changed files with 441 additions and 205 deletions
|
@ -16,12 +16,20 @@ const maxArraySize = 0x1000000
|
|||
// Used to simplify error handling when reading into a struct with many fields.
|
||||
type BinReader struct {
|
||||
r io.Reader
|
||||
u64 []byte
|
||||
u32 []byte
|
||||
u16 []byte
|
||||
u8 []byte
|
||||
Err error
|
||||
}
|
||||
|
||||
// NewBinReaderFromIO makes a BinReader from io.Reader.
|
||||
func NewBinReaderFromIO(ior io.Reader) *BinReader {
|
||||
return &BinReader{r: ior}
|
||||
u64 := make([]byte, 8)
|
||||
u32 := u64[:4]
|
||||
u16 := u64[:2]
|
||||
u8 := u64[:1]
|
||||
return &BinReader{r: ior, u64: u64, u32: u32, u16: u16, u8: u8}
|
||||
}
|
||||
|
||||
// NewBinReaderFromBuf makes a BinReader from byte buffer.
|
||||
|
@ -39,6 +47,62 @@ func (r *BinReader) ReadLE(v interface{}) {
|
|||
r.Err = binary.Read(r.r, binary.LittleEndian, v)
|
||||
}
|
||||
|
||||
// ReadU64LE reads a little-endian encoded uint64 value from the underlying
|
||||
// io.Reader. On read failures it returns zero.
|
||||
func (r *BinReader) ReadU64LE() uint64 {
|
||||
r.ReadBytes(r.u64)
|
||||
if r.Err != nil {
|
||||
return 0
|
||||
}
|
||||
return binary.LittleEndian.Uint64(r.u64)
|
||||
}
|
||||
|
||||
// ReadU32LE reads a little-endian encoded uint32 value from the underlying
|
||||
// io.Reader. On read failures it returns zero.
|
||||
func (r *BinReader) ReadU32LE() uint32 {
|
||||
r.ReadBytes(r.u32)
|
||||
if r.Err != nil {
|
||||
return 0
|
||||
}
|
||||
return binary.LittleEndian.Uint32(r.u32)
|
||||
}
|
||||
|
||||
// ReadU16LE reads a little-endian encoded uint16 value from the underlying
|
||||
// io.Reader. On read failures it returns zero.
|
||||
func (r *BinReader) ReadU16LE() uint16 {
|
||||
r.ReadBytes(r.u16)
|
||||
if r.Err != nil {
|
||||
return 0
|
||||
}
|
||||
return binary.LittleEndian.Uint16(r.u16)
|
||||
}
|
||||
|
||||
// ReadU16BE reads a big-endian encoded uint16 value from the underlying
|
||||
// io.Reader. On read failures it returns zero.
|
||||
func (r *BinReader) ReadU16BE() uint16 {
|
||||
r.ReadBytes(r.u16)
|
||||
if r.Err != nil {
|
||||
return 0
|
||||
}
|
||||
return binary.BigEndian.Uint16(r.u16)
|
||||
}
|
||||
|
||||
// ReadByte reads a byte from the underlying io.Reader. On read failures it
|
||||
// returns zero.
|
||||
func (r *BinReader) ReadByte() byte {
|
||||
r.ReadBytes(r.u8)
|
||||
if r.Err != nil {
|
||||
return 0
|
||||
}
|
||||
return r.u8[0]
|
||||
}
|
||||
|
||||
// ReadBool reads a boolean value encoded in a zero/non-zero byte from the
|
||||
// underlying io.Reader. On read failures it returns false.
|
||||
func (r *BinReader) ReadBool() bool {
|
||||
return r.ReadByte() != 0
|
||||
}
|
||||
|
||||
// ReadArray reads array into value which must be
|
||||
// a pointer to a slice.
|
||||
func (r *BinReader) ReadArray(t interface{}, maxSize ...int) {
|
||||
|
|
|
@ -11,12 +11,20 @@ import (
|
|||
// from a struct with many fields.
|
||||
type BinWriter struct {
|
||||
w io.Writer
|
||||
u64 []byte
|
||||
u32 []byte
|
||||
u16 []byte
|
||||
u8 []byte
|
||||
Err error
|
||||
}
|
||||
|
||||
// NewBinWriterFromIO makes a BinWriter from io.Writer.
|
||||
func NewBinWriterFromIO(iow io.Writer) *BinWriter {
|
||||
return &BinWriter{w: iow}
|
||||
u64 := make([]byte, 8)
|
||||
u32 := u64[:4]
|
||||
u16 := u64[:2]
|
||||
u8 := u64[:1]
|
||||
return &BinWriter{w: iow, u64: u64, u32: u32, u16: u16, u8: u8}
|
||||
}
|
||||
|
||||
// WriteLE writes into the underlying io.Writer from an object v in little-endian format.
|
||||
|
@ -35,6 +43,50 @@ func (w *BinWriter) WriteBE(v interface{}) {
|
|||
w.Err = binary.Write(w.w, binary.BigEndian, v)
|
||||
}
|
||||
|
||||
// WriteU64LE writes an uint64 value into the underlying io.Writer in
|
||||
// little-endian format.
|
||||
func (w *BinWriter) WriteU64LE(u64 uint64) {
|
||||
binary.LittleEndian.PutUint64(w.u64, u64)
|
||||
w.WriteBytes(w.u64)
|
||||
}
|
||||
|
||||
// WriteU32LE writes an uint32 value into the underlying io.Writer in
|
||||
// little-endian format.
|
||||
func (w *BinWriter) WriteU32LE(u32 uint32) {
|
||||
binary.LittleEndian.PutUint32(w.u32, u32)
|
||||
w.WriteBytes(w.u32)
|
||||
}
|
||||
|
||||
// WriteU16LE writes an uint16 value into the underlying io.Writer in
|
||||
// little-endian format.
|
||||
func (w *BinWriter) WriteU16LE(u16 uint16) {
|
||||
binary.LittleEndian.PutUint16(w.u16, u16)
|
||||
w.WriteBytes(w.u16)
|
||||
}
|
||||
|
||||
// WriteU16BE writes an uint16 value into the underlying io.Writer in
|
||||
// big-endian format.
|
||||
func (w *BinWriter) WriteU16BE(u16 uint16) {
|
||||
binary.BigEndian.PutUint16(w.u16, u16)
|
||||
w.WriteBytes(w.u16)
|
||||
}
|
||||
|
||||
// WriteByte writes a byte into the underlying io.Writer.
|
||||
func (w *BinWriter) WriteByte(u8 byte) {
|
||||
w.u8[0] = u8
|
||||
w.WriteBytes(w.u8)
|
||||
}
|
||||
|
||||
// WriteBool writes a boolean value into the underlying io.Writer encoded as
|
||||
// a byte with values of 0 or 1.
|
||||
func (w *BinWriter) WriteBool(b bool) {
|
||||
var i byte
|
||||
if b {
|
||||
i = 1
|
||||
}
|
||||
w.WriteByte(i)
|
||||
}
|
||||
|
||||
// WriteArray writes a slice or an array arr into w. Note that nil slices and
|
||||
// empty slices are gonna be treated the same resulting in equal zero-length
|
||||
// array encoded.
|
||||
|
|
|
@ -53,6 +53,123 @@ func TestWriteBE(t *testing.T) {
|
|||
assert.Equal(t, val, readval)
|
||||
}
|
||||
|
||||
func TestWriteU64LE(t *testing.T) {
|
||||
var (
|
||||
val uint64 = 0xbadc0de15a11dead
|
||||
readval uint64
|
||||
bin = []byte{0xad, 0xde, 0x11, 0x5a, 0xe1, 0x0d, 0xdc, 0xba}
|
||||
)
|
||||
bw := NewBufBinWriter()
|
||||
bw.WriteU64LE(val)
|
||||
assert.Nil(t, bw.Err)
|
||||
wrotebin := bw.Bytes()
|
||||
assert.Equal(t, wrotebin, bin)
|
||||
br := NewBinReaderFromBuf(bin)
|
||||
readval = br.ReadU64LE()
|
||||
assert.Nil(t, br.Err)
|
||||
assert.Equal(t, val, readval)
|
||||
}
|
||||
|
||||
func TestWriteU32LE(t *testing.T) {
|
||||
var (
|
||||
val uint32 = 0xdeadbeef
|
||||
readval uint32
|
||||
bin = []byte{0xef, 0xbe, 0xad, 0xde}
|
||||
)
|
||||
bw := NewBufBinWriter()
|
||||
bw.WriteU32LE(val)
|
||||
assert.Nil(t, bw.Err)
|
||||
wrotebin := bw.Bytes()
|
||||
assert.Equal(t, wrotebin, bin)
|
||||
br := NewBinReaderFromBuf(bin)
|
||||
readval = br.ReadU32LE()
|
||||
assert.Nil(t, br.Err)
|
||||
assert.Equal(t, val, readval)
|
||||
}
|
||||
|
||||
func TestWriteU16LE(t *testing.T) {
|
||||
var (
|
||||
val uint16 = 0xbabe
|
||||
readval uint16
|
||||
bin = []byte{0xbe, 0xba}
|
||||
)
|
||||
bw := NewBufBinWriter()
|
||||
bw.WriteU16LE(val)
|
||||
assert.Nil(t, bw.Err)
|
||||
wrotebin := bw.Bytes()
|
||||
assert.Equal(t, wrotebin, bin)
|
||||
br := NewBinReaderFromBuf(bin)
|
||||
readval = br.ReadU16LE()
|
||||
assert.Nil(t, br.Err)
|
||||
assert.Equal(t, val, readval)
|
||||
}
|
||||
|
||||
func TestWriteU16BE(t *testing.T) {
|
||||
var (
|
||||
val uint16 = 0xbabe
|
||||
readval uint16
|
||||
bin = []byte{0xba, 0xbe}
|
||||
)
|
||||
bw := NewBufBinWriter()
|
||||
bw.WriteU16BE(val)
|
||||
assert.Nil(t, bw.Err)
|
||||
wrotebin := bw.Bytes()
|
||||
assert.Equal(t, wrotebin, bin)
|
||||
br := NewBinReaderFromBuf(bin)
|
||||
readval = br.ReadU16BE()
|
||||
assert.Nil(t, br.Err)
|
||||
assert.Equal(t, val, readval)
|
||||
}
|
||||
|
||||
func TestWriteByte(t *testing.T) {
|
||||
var (
|
||||
val byte = 0xa5
|
||||
readval byte
|
||||
bin = []byte{0xa5}
|
||||
)
|
||||
bw := NewBufBinWriter()
|
||||
bw.WriteByte(val)
|
||||
assert.Nil(t, bw.Err)
|
||||
wrotebin := bw.Bytes()
|
||||
assert.Equal(t, wrotebin, bin)
|
||||
br := NewBinReaderFromBuf(bin)
|
||||
readval = br.ReadByte()
|
||||
assert.Nil(t, br.Err)
|
||||
assert.Equal(t, val, readval)
|
||||
}
|
||||
|
||||
func TestWriteBool(t *testing.T) {
|
||||
var (
|
||||
bin = []byte{0x01, 0x00}
|
||||
)
|
||||
bw := NewBufBinWriter()
|
||||
bw.WriteBool(true)
|
||||
bw.WriteBool(false)
|
||||
assert.Nil(t, bw.Err)
|
||||
wrotebin := bw.Bytes()
|
||||
assert.Equal(t, wrotebin, bin)
|
||||
br := NewBinReaderFromBuf(bin)
|
||||
assert.Equal(t, true, br.ReadBool())
|
||||
assert.Equal(t, false, br.ReadBool())
|
||||
assert.Nil(t, br.Err)
|
||||
}
|
||||
|
||||
func TestReadLEErrors(t *testing.T) {
|
||||
bin := []byte{0xad, 0xde, 0x11, 0x5a, 0xe1, 0x0d, 0xdc, 0xba}
|
||||
br := NewBinReaderFromBuf(bin)
|
||||
// Prime the buffers with something.
|
||||
_ = br.ReadU64LE()
|
||||
assert.Nil(t, br.Err)
|
||||
|
||||
assert.Equal(t, uint64(0), br.ReadU64LE())
|
||||
assert.Equal(t, uint32(0), br.ReadU32LE())
|
||||
assert.Equal(t, uint16(0), br.ReadU16LE())
|
||||
assert.Equal(t, uint16(0), br.ReadU16BE())
|
||||
assert.Equal(t, byte(0), br.ReadByte())
|
||||
assert.Equal(t, false, br.ReadBool())
|
||||
assert.NotNil(t, br.Err)
|
||||
}
|
||||
|
||||
func TestBufBinWriter_Len(t *testing.T) {
|
||||
val := []byte{0xde}
|
||||
bw := NewBufBinWriter()
|
||||
|
|
|
@ -17,7 +17,7 @@ type smthSerializable struct {
|
|||
func (*smthSerializable) DecodeBinary(*io.BinReader) {}
|
||||
|
||||
func (ss *smthSerializable) EncodeBinary(bw *io.BinWriter) {
|
||||
bw.WriteLE(ss.some)
|
||||
bw.WriteBytes(ss.some[:])
|
||||
}
|
||||
|
||||
// Mock structure that gives error in EncodeBinary().
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue