wire: Avoid allocation on timestamp decodes.

Since the protocol encodes timestamps differently depending on the
message, the code currently decodes into a local variable and then
converts it to a time.Time.  However, this causes an allocation due to
the local having to escape to the heap in order for the readElement
function to write to it.

So, in order to avoid that, this introduces two new types for a
timestamp named uint32Time and int64Time that are encoded as the
respective type on the read.  When calling the readElements function,
the time.Time field in the message is cast to a pointer of the
appropriate type which effectively allows the allocations to be avoided.

The following is a before and after comparison of the allocations
with the benchmarks that did not change removed:

benchmark              old allocs     new allocs     delta
----------------------------------------------------------------------
ReadBlockHeader        1              0              -100.00%
DecodeHeaders          4001           2001           -49.99%
DecodeAddr             4001           3001           -24.99%
DecodeMerkleBlock      108            107            -0.93%
This commit is contained in:
Dave Collins 2016-04-21 00:38:00 -05:00
parent f68cd7422d
commit 5de5b7354c
4 changed files with 34 additions and 12 deletions

View file

@ -115,13 +115,11 @@ func NewBlockHeader(prevHash *ShaHash, merkleRootHash *ShaHash, bits uint32,
// decoding block headers stored to disk, such as in a database, as opposed to
// decoding from the wire.
func readBlockHeader(r io.Reader, pver uint32, bh *BlockHeader) error {
var sec uint32
err := readElements(r, &bh.Version, &bh.PrevBlock, &bh.MerkleRoot, &sec,
&bh.Bits, &bh.Nonce)
err := readElements(r, &bh.Version, &bh.PrevBlock, &bh.MerkleRoot,
(*uint32Time)(&bh.Timestamp), &bh.Bits, &bh.Nonce)
if err != nil {
return err
}
bh.Timestamp = time.Unix(int64(sec), 0)
return nil
}

View file

@ -10,6 +10,7 @@ import (
"fmt"
"io"
"math"
"time"
"github.com/btcsuite/fastsha256"
)
@ -175,6 +176,16 @@ var binarySerializer binaryFreeList = make(chan []byte, binaryFreeListMaxItems)
var errNonCanonicalVarInt = "non-canonical varint %x - discriminant %x must " +
"encode a value greater than %x"
// uint32Time represents a unix timestamp encoded with a uint32. It is used as
// a way to signal the readElement function how to decode a timestamp into a Go
// time.Time since it is otherwise ambiguous.
type uint32Time time.Time
// int64Time represents a unix timestamp encoded with an int64. It is used as
// a way to signal the readElement function how to decode a timestamp into a Go
// time.Time since it is otherwise ambiguous.
type int64Time time.Time
// readElement reads the next sequence of bytes from r using little endian
// depending on the concrete type of element pointed to.
func readElement(r io.Reader, element interface{}) error {
@ -225,6 +236,24 @@ func readElement(r io.Reader, element interface{}) error {
}
return nil
// Unix timestamp encoded as a uint32.
case *uint32Time:
rv, err := binarySerializer.Uint32(r, binary.LittleEndian)
if err != nil {
return err
}
*e = uint32Time(time.Unix(int64(rv), 0))
return nil
// Unix timestamp encoded as an int64.
case *int64Time:
rv, err := binarySerializer.Uint64(r, binary.LittleEndian)
if err != nil {
return err
}
*e = int64Time(time.Unix(int64(rv), 0))
return nil
// Message header checksum.
case *[4]byte:
_, err := io.ReadFull(r, e[:])

View file

@ -84,12 +84,11 @@ func (msg *MsgVersion) BtcDecode(r io.Reader, pver uint32) error {
"*bytes.Buffer")
}
var sec int64
err := readElements(buf, &msg.ProtocolVersion, &msg.Services, &sec)
err := readElements(buf, &msg.ProtocolVersion, &msg.Services,
(*int64Time)(&msg.Timestamp))
if err != nil {
return err
}
msg.Timestamp = time.Unix(sec, 0)
err = readNetAddress(buf, pver, &msg.AddrYou, false)
if err != nil {

View file

@ -105,7 +105,6 @@ func NewNetAddress(addr net.Addr, services ServiceFlag) (*NetAddress, error) {
// version and whether or not the timestamp is included per ts. Some messages
// like version do not include the timestamp.
func readNetAddress(r io.Reader, pver uint32, na *NetAddress, ts bool) error {
var timestamp time.Time
var services ServiceFlag
var ip [16]byte
@ -113,12 +112,10 @@ func readNetAddress(r io.Reader, pver uint32, na *NetAddress, ts bool) error {
// stop working somewhere around 2106. Also timestamp wasn't added until
// protocol version >= NetAddressTimeVersion
if ts && pver >= NetAddressTimeVersion {
var stamp uint32
err := readElement(r, &stamp)
err := readElement(r, (*uint32Time)(&na.Timestamp))
if err != nil {
return err
}
timestamp = time.Unix(int64(stamp), 0)
}
err := readElements(r, &services, &ip)
@ -131,7 +128,6 @@ func readNetAddress(r io.Reader, pver uint32, na *NetAddress, ts bool) error {
return err
}
na.Timestamp = timestamp
na.Services = services
na.SetAddress(net.IP(ip[:]), port)
return nil