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:
parent
f68cd7422d
commit
5de5b7354c
4 changed files with 34 additions and 12 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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[:])
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue