Optimize writeElement.

This commit modifies the writeElement function to have a "fast path" which uses type
assertions for all of the types which btcwire write so the more expensive
reflection-based binary.Write can be avoided.

Also, this changes all cases that were writing raw ShaHash (32-byte) arrays (which
requires a stack copy) instead simply passing the pointer.

The following benchmark results show the results for serializing a block header
after these changes:

Before: BenchmarkWriteBlockHeader         500000              5566 ns/op
After:  BenchmarkWriteBlockHeader        1000000               991 ns/op

This is part of the ongoing effort to optimize serialization as noted in
conformal/btcd#27.
This commit is contained in:
Dave Collins 2013-11-07 00:56:20 -06:00
parent 7de20add63
commit 9ee6a8aeb6
5 changed files with 105 additions and 7 deletions

View file

@ -107,7 +107,7 @@ func readBlockHeader(r io.Reader, pver uint32, bh *BlockHeader) error {
// writeBlockHeader writes a bitcoin block header to w. // writeBlockHeader writes a bitcoin block header to w.
func writeBlockHeader(w io.Writer, pver uint32, bh *BlockHeader) error { func writeBlockHeader(w io.Writer, pver uint32, bh *BlockHeader) error {
sec := uint32(bh.Timestamp.Unix()) sec := uint32(bh.Timestamp.Unix())
err := writeElements(w, bh.Version, bh.PrevBlock, bh.MerkleRoot, err := writeElements(w, bh.Version, &bh.PrevBlock, &bh.MerkleRoot,
sec, bh.Bits, bh.Nonce) sec, bh.Bits, bh.Nonce)
if err != nil { if err != nil {
return err return err

104
common.go
View file

@ -17,9 +17,7 @@ import (
const maxVarIntPayload = 9 const maxVarIntPayload = 9
// readElement reads the next sequence of bytes from r using little endian // readElement reads the next sequence of bytes from r using little endian
// depending on the concrete type of element pointed to. It also accepts a // depending on the concrete type of element pointed to.
// scratch buffer that is used for the primitive values rather than creating
// a new buffer on every call.
func readElement(r io.Reader, element interface{}) error { func readElement(r io.Reader, element interface{}) error {
var scratch [8]byte var scratch [8]byte
@ -140,6 +138,106 @@ func readElements(r io.Reader, elements ...interface{}) error {
// writeElement writes the little endian representation of element to w. // writeElement writes the little endian representation of element to w.
func writeElement(w io.Writer, element interface{}) error { func writeElement(w io.Writer, element interface{}) error {
var scratch [8]byte
// Attempt to read the element based on the concrete type via fast
// type assertions first.
switch e := element.(type) {
case int32:
b := scratch[0:4]
binary.LittleEndian.PutUint32(b, uint32(e))
_, err := w.Write(b)
if err != nil {
return err
}
return nil
case uint32:
b := scratch[0:4]
binary.LittleEndian.PutUint32(b, e)
_, err := w.Write(b)
if err != nil {
return err
}
return nil
case int64:
b := scratch[0:8]
binary.LittleEndian.PutUint64(b, uint64(e))
_, err := w.Write(b)
if err != nil {
return err
}
return nil
case uint64:
b := scratch[0:8]
binary.LittleEndian.PutUint64(b, e)
_, err := w.Write(b)
if err != nil {
return err
}
return nil
// Message header checksum.
case [4]byte:
_, err := w.Write(e[:])
if err != nil {
return err
}
return nil
// Message header command.
case [commandSize]uint8:
_, err := w.Write(e[:])
if err != nil {
return err
}
return nil
// IP address.
case [16]byte:
_, err := w.Write(e[:])
if err != nil {
return err
}
return nil
case *ShaHash:
_, err := w.Write(e[:])
if err != nil {
return err
}
return nil
case ServiceFlag:
b := scratch[0:8]
binary.LittleEndian.PutUint64(b, uint64(e))
_, err := w.Write(b)
if err != nil {
return err
}
return nil
case InvType:
b := scratch[0:4]
binary.LittleEndian.PutUint32(b, uint32(e))
_, err := w.Write(b)
if err != nil {
return err
}
return nil
case BitcoinNet:
b := scratch[0:4]
binary.LittleEndian.PutUint32(b, uint32(e))
_, err := w.Write(b)
if err != nil {
return err
}
return nil
}
return binary.Write(w, binary.LittleEndian, element) return binary.Write(w, binary.LittleEndian, element)
} }

View file

@ -72,7 +72,7 @@ func readInvVect(r io.Reader, pver uint32, iv *InvVect) error {
// writeInvVect serializes an InvVect to w depending on the protocol version. // writeInvVect serializes an InvVect to w depending on the protocol version.
func writeInvVect(w io.Writer, pver uint32, iv *InvVect) error { func writeInvVect(w io.Writer, pver uint32, iv *InvVect) error {
err := writeElements(w, iv.Type, iv.Hash) err := writeElements(w, iv.Type, &iv.Hash)
if err != nil { if err != nil {
return err return err
} }

View file

@ -110,7 +110,7 @@ func (msg *MsgGetBlocks) BtcEncode(w io.Writer, pver uint32) error {
} }
} }
err = writeElement(w, msg.HashStop) err = writeElement(w, &msg.HashStop)
if err != nil { if err != nil {
return err return err
} }

View file

@ -108,7 +108,7 @@ func (msg *MsgGetHeaders) BtcEncode(w io.Writer, pver uint32) error {
} }
} }
err = writeElement(w, msg.HashStop) err = writeElement(w, &msg.HashStop)
if err != nil { if err != nil {
return err return err
} }