From 5f971e10e626cdb48d2ef0dd834d943408274280 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 25 Sep 2013 13:57:01 -0500 Subject: [PATCH] Pre-allocate space for slices. Several of the messages store the parts that have a variable number of elements as slices. This commit modifies the code to choose sane defaults for the backing arrays for the slices so when the entries are actually appended, a lot of the overhead of growing the backing arrays and copying the data multiple times is avoided. Along the same lines, when decoding messages, the actual size is known and now is pre-allocated instead of dynamically growing the backing array thereby avoiding some overhead. --- msgaddr.go | 5 ++++- msgblock.go | 16 +++++++++++++--- msggetblocks.go | 6 ++++-- msggetdata.go | 5 ++++- msggetheaders.go | 5 ++++- msgheaders.go | 5 ++++- msginv.go | 14 +++++++++++++- msgnotfound.go | 5 ++++- msgtx.go | 20 ++++++++++++++++++-- test_coverage.txt | 30 +++++++++++++++--------------- 10 files changed, 83 insertions(+), 28 deletions(-) diff --git a/msgaddr.go b/msgaddr.go index 13371c5c..b4dcfb12 100644 --- a/msgaddr.go +++ b/msgaddr.go @@ -70,6 +70,7 @@ func (msg *MsgAddr) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgAddr.BtcDecode", str) } + msg.AddrList = make([]*NetAddress, 0, count) for i := uint64(0); i < count; i++ { na := NetAddress{} err := readNetAddress(r, pver, &na, true) @@ -135,5 +136,7 @@ func (msg *MsgAddr) MaxPayloadLength(pver uint32) uint32 { // NewMsgAddr returns a new bitcoin addr message that conforms to the // Message interface. See MsgAddr for details. func NewMsgAddr() *MsgAddr { - return &MsgAddr{} + return &MsgAddr{ + AddrList: make([]*NetAddress, 0, MaxAddrPerMsg), + } } diff --git a/msgblock.go b/msgblock.go index 5f6d72b8..7ad79a3e 100644 --- a/msgblock.go +++ b/msgblock.go @@ -9,6 +9,13 @@ import ( "io" ) +// defaultTransactionAlloc is the default size used for the backing array +// for transactions. The transaction array will dynamically grow as needed, but +// this figure is intended to provide enough space for the number of +// transactions in the vast majority of blocks without needing to grow the +// backing array multiple times. +const defaultTransactionAlloc = 2048 + // MaxBlocksPerMsg is the maximum number of blocks allowed per message. const MaxBlocksPerMsg = 500 @@ -51,7 +58,7 @@ func (msg *MsgBlock) AddTransaction(tx *MsgTx) error { // ClearTransactions removes all transactions from the message and updates // Header.TxnCount accordingly. func (msg *MsgBlock) ClearTransactions() { - msg.Transactions = []*MsgTx{} + msg.Transactions = make([]*MsgTx, 0, defaultTransactionAlloc) msg.Header.TxnCount = 0 } @@ -65,6 +72,7 @@ func (msg *MsgBlock) BtcDecode(r io.Reader, pver uint32) error { return err } + msg.Transactions = make([]*MsgTx, 0, msg.Header.TxnCount) for i := uint64(0); i < msg.Header.TxnCount; i++ { tx := MsgTx{} err := tx.BtcDecode(r, pver) @@ -110,6 +118,7 @@ func (msg *MsgBlock) DeserializeTxLoc(r *bytes.Buffer) ([]TxLoc, error) { // Deserialize each transaction while keeping track of its location // within the byte stream. txCount := msg.Header.TxnCount + msg.Transactions = make([]*MsgTx, 0, txCount) txLocs := make([]TxLoc, txCount) for i := uint64(0); i < txCount; i++ { txLocs[i].TxStart = fullLen - r.Len() @@ -184,7 +193,7 @@ func (msg *MsgBlock) BlockSha() (ShaHash, error) { // TxShas returns a slice of hashes of all of transactions in this block. func (msg *MsgBlock) TxShas() ([]ShaHash, error) { - var shaList []ShaHash + shaList := make([]ShaHash, 0, len(msg.Transactions)) for _, tx := range msg.Transactions { // Ignore error here since TxSha can't fail in the current // implementation except due to run-time panics. @@ -198,6 +207,7 @@ func (msg *MsgBlock) TxShas() ([]ShaHash, error) { // Message interface. See MsgBlock for details. func NewMsgBlock(blockHeader *BlockHeader) *MsgBlock { return &MsgBlock{ - Header: *blockHeader, + Header: *blockHeader, + Transactions: make([]*MsgTx, 0, defaultTransactionAlloc), } } diff --git a/msggetblocks.go b/msggetblocks.go index b10eb7e6..9f083630 100644 --- a/msggetblocks.go +++ b/msggetblocks.go @@ -65,6 +65,7 @@ func (msg *MsgGetBlocks) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgGetBlocks.BtcDecode", str) } + msg.BlockLocatorHashes = make([]*ShaHash, 0, count) for i := uint64(0); i < count; i++ { sha := ShaHash{} err := readElement(r, &sha) @@ -136,7 +137,8 @@ func (msg *MsgGetBlocks) MaxPayloadLength(pver uint32) uint32 { // fields. func NewMsgGetBlocks(hashStop *ShaHash) *MsgGetBlocks { return &MsgGetBlocks{ - ProtocolVersion: ProtocolVersion, - HashStop: *hashStop, + ProtocolVersion: ProtocolVersion, + BlockLocatorHashes: make([]*ShaHash, 0, MaxBlockLocatorsPerMsg), + HashStop: *hashStop, } } diff --git a/msggetdata.go b/msggetdata.go index e4f546eb..c58d89cb 100644 --- a/msggetdata.go +++ b/msggetdata.go @@ -49,6 +49,7 @@ func (msg *MsgGetData) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgGetData.BtcDecode", str) } + msg.InvList = make([]*InvVect, 0, count) for i := uint64(0); i < count; i++ { iv := InvVect{} err := readInvVect(r, pver, &iv) @@ -102,5 +103,7 @@ func (msg *MsgGetData) MaxPayloadLength(pver uint32) uint32 { // NewMsgGetData returns a new bitcoin getdata message that conforms to the // Message interface. See MsgGetData for details. func NewMsgGetData() *MsgGetData { - return &MsgGetData{} + return &MsgGetData{ + InvList: make([]*InvVect, 0, defaultInvListAlloc), + } } diff --git a/msggetheaders.go b/msggetheaders.go index 50a6b023..b360203a 100644 --- a/msggetheaders.go +++ b/msggetheaders.go @@ -62,6 +62,7 @@ func (msg *MsgGetHeaders) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgGetHeaders.BtcDecode", str) } + msg.BlockLocatorHashes = make([]*ShaHash, 0, count) for i := uint64(0); i < count; i++ { sha := ShaHash{} err := readElement(r, &sha) @@ -132,5 +133,7 @@ func (msg *MsgGetHeaders) MaxPayloadLength(pver uint32) uint32 { // NewMsgGetHeaders returns a new bitcoin getheaders message that conforms to // the Message interface. See MsgGetHeaders for details. func NewMsgGetHeaders() *MsgGetHeaders { - return &MsgGetHeaders{} + return &MsgGetHeaders{ + BlockLocatorHashes: make([]*ShaHash, 0, MaxBlockLocatorsPerMsg), + } } diff --git a/msgheaders.go b/msgheaders.go index 3aa5d160..294955a1 100644 --- a/msgheaders.go +++ b/msgheaders.go @@ -49,6 +49,7 @@ func (msg *MsgHeaders) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgHeaders.BtcDecode", str) } + msg.Headers = make([]*BlockHeader, 0, count) for i := uint64(0); i < count; i++ { bh := BlockHeader{} err := readBlockHeader(r, pver, &bh) @@ -117,5 +118,7 @@ func (msg *MsgHeaders) MaxPayloadLength(pver uint32) uint32 { // NewMsgHeaders returns a new bitcoin headers message that conforms to the // Message interface. See MsgHeaders for details. func NewMsgHeaders() *MsgHeaders { - return &MsgHeaders{} + return &MsgHeaders{ + Headers: make([]*BlockHeader, 0, MaxBlockHeadersPerMsg), + } } diff --git a/msginv.go b/msginv.go index 015f4a81..96538af8 100644 --- a/msginv.go +++ b/msginv.go @@ -9,6 +9,15 @@ import ( "io" ) +// defaultInvListAlloc is the default size used for the backing array for an +// inventory list. The array will dynamically grow as needed, but this +// figure is intended to provide enough space for the max number of inventory +// vectors in a *typical* inventory message without needing to grow the backing +// array multiple times. Technically, the list can grow to MaxInvPerMsg, but +// rather than using that large figure, this figure more accurately reflects the +// typical case. +const defaultInvListAlloc = 1000 + // MsgInv implements the Message interface and represents a bitcoin inv message. // It is used to advertise a peer's known data such as blocks and transactions // through inventory vectors. It may be sent unsolicited to inform other peers @@ -48,6 +57,7 @@ func (msg *MsgInv) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgInv.BtcDecode", str) } + msg.InvList = make([]*InvVect, 0, count) for i := uint64(0); i < count; i++ { iv := InvVect{} err := readInvVect(r, pver, &iv) @@ -101,5 +111,7 @@ func (msg *MsgInv) MaxPayloadLength(pver uint32) uint32 { // NewMsgInv returns a new bitcoin inv message that conforms to the Message // interface. See MsgInv for details. func NewMsgInv() *MsgInv { - return &MsgInv{} + return &MsgInv{ + InvList: make([]*InvVect, 0, defaultInvListAlloc), + } } diff --git a/msgnotfound.go b/msgnotfound.go index 1fbfd155..0370d104 100644 --- a/msgnotfound.go +++ b/msgnotfound.go @@ -46,6 +46,7 @@ func (msg *MsgNotFound) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgNotFound.BtcDecode", str) } + msg.InvList = make([]*InvVect, 0, count) for i := uint64(0); i < count; i++ { iv := InvVect{} err := readInvVect(r, pver, &iv) @@ -100,5 +101,7 @@ func (msg *MsgNotFound) MaxPayloadLength(pver uint32) uint32 { // NewMsgNotFound returns a new bitcoin notfound message that conforms to the // Message interface. See MsgNotFound for details. func NewMsgNotFound() *MsgNotFound { - return &MsgNotFound{} + return &MsgNotFound{ + InvList: make([]*InvVect, 0, defaultInvListAlloc), + } } diff --git a/msgtx.go b/msgtx.go index 0e5b1d1b..cdccbcc7 100644 --- a/msgtx.go +++ b/msgtx.go @@ -9,6 +9,13 @@ import ( "io" ) +// defaultTxInOutAlloc is the default size used for the backing array for +// transaction inputs and outputs. The array will dynamically grow as needed, +// but this figure is intended to provide enough space for the number of +// inputs and outputs in a typical transaction without needing to grow the +// backing array multiple times. +const defaultTxInOutAlloc = 15 + // TxVersion is the current latest supported transaction version. const TxVersion = 1 @@ -110,9 +117,12 @@ func (tx *MsgTx) TxSha() (ShaHash, error) { // Copy creates a deep copy of a transaction so that the original does not get // modified when the copy is manipulated. func (tx *MsgTx) Copy() *MsgTx { - // Create new tx and start by copying primitive values. + // Create new tx and start by copying primitive values and making space + // for the transaction inputs and outputs. newTx := MsgTx{ Version: tx.Version, + TxIn: make([]*TxIn, 0, len(tx.TxIn)), + TxOut: make([]*TxOut, 0, len(tx.TxOut)), LockTime: tx.LockTime, } @@ -181,6 +191,7 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32) error { return err } + msg.TxIn = make([]*TxIn, 0, count) for i := uint64(0); i < count; i++ { ti := TxIn{} err = readTxIn(r, pver, msg.Version, &ti) @@ -195,6 +206,7 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32) error { return err } + msg.TxOut = make([]*TxOut, 0, count) for i := uint64(0); i < count; i++ { to := TxOut{} err = readTxOut(r, pver, msg.Version, &to) @@ -309,7 +321,11 @@ func (msg *MsgTx) MaxPayloadLength(pver uint32) uint32 { // to indicate the transaction is valid immediately as opposed to some time in // future. func NewMsgTx() *MsgTx { - return &MsgTx{Version: TxVersion} + return &MsgTx{ + Version: TxVersion, + TxIn: make([]*TxIn, 0, defaultTxInOutAlloc), + TxOut: make([]*TxOut, 0, defaultTxInOutAlloc), + } } // readOutPoint reads the next sequence of bytes from r as an OutPoint. diff --git a/test_coverage.txt b/test_coverage.txt index 7a6c0081..2dd4557f 100644 --- a/test_coverage.txt +++ b/test_coverage.txt @@ -1,49 +1,49 @@ github.com/conformal/btcwire/message.go ReadMessage 100.00% (37/37) github.com/conformal/btcwire/message.go WriteMessage 100.00% (31/31) +github.com/conformal/btcwire/msgtx.go MsgTx.BtcDecode 100.00% (27/27) github.com/conformal/btcwire/msgversion.go MsgVersion.BtcDecode 100.00% (25/25) -github.com/conformal/btcwire/msgtx.go MsgTx.BtcDecode 100.00% (25/25) github.com/conformal/btcwire/common.go readVarInt 100.00% (24/24) github.com/conformal/btcwire/msgtx.go MsgTx.Copy 100.00% (24/24) github.com/conformal/btcwire/msgtx.go MsgTx.BtcEncode 100.00% (23/23) github.com/conformal/btcwire/msgversion.go MsgVersion.BtcEncode 100.00% (22/22) +github.com/conformal/btcwire/msggetheaders.go MsgGetHeaders.BtcDecode 100.00% (20/20) +github.com/conformal/btcwire/msggetblocks.go MsgGetBlocks.BtcDecode 100.00% (20/20) github.com/conformal/btcwire/message.go makeEmptyMessage 100.00% (20/20) github.com/conformal/btcwire/netaddress.go readNetAddress 100.00% (20/20) -github.com/conformal/btcwire/msggetheaders.go MsgGetHeaders.BtcDecode 100.00% (19/19) -github.com/conformal/btcwire/msggetblocks.go MsgGetBlocks.BtcDecode 100.00% (19/19) github.com/conformal/btcwire/msggetheaders.go MsgGetHeaders.BtcEncode 100.00% (18/18) github.com/conformal/btcwire/msggetblocks.go MsgGetBlocks.BtcEncode 100.00% (18/18) +github.com/conformal/btcwire/msgheaders.go MsgHeaders.BtcDecode 100.00% (17/17) github.com/conformal/btcwire/msgtx.go readTxIn 100.00% (17/17) github.com/conformal/btcwire/common.go writeVarInt 100.00% (16/16) -github.com/conformal/btcwire/msgheaders.go MsgHeaders.BtcDecode 100.00% (16/16) -github.com/conformal/btcwire/msgblock.go MsgBlock.DeserializeTxLoc 100.00% (15/15) +github.com/conformal/btcwire/msgblock.go MsgBlock.DeserializeTxLoc 100.00% (16/16) +github.com/conformal/btcwire/msgheaders.go MsgHeaders.BtcEncode 100.00% (15/15) github.com/conformal/btcwire/msgaddr.go MsgAddr.BtcEncode 100.00% (15/15) github.com/conformal/btcwire/msgtx.go writeTxIn 100.00% (15/15) github.com/conformal/btcwire/shahash.go NewShaHashFromStr 100.00% (15/15) -github.com/conformal/btcwire/msgheaders.go MsgHeaders.BtcEncode 100.00% (15/15) github.com/conformal/btcwire/netaddress.go writeNetAddress 100.00% (14/14) -github.com/conformal/btcwire/msgnotfound.go MsgNotFound.BtcDecode 100.00% (13/13) -github.com/conformal/btcwire/msginv.go MsgInv.BtcDecode 100.00% (13/13) -github.com/conformal/btcwire/msggetdata.go MsgGetData.BtcDecode 100.00% (13/13) -github.com/conformal/btcwire/msgaddr.go MsgAddr.BtcDecode 100.00% (13/13) +github.com/conformal/btcwire/msggetdata.go MsgGetData.BtcDecode 100.00% (14/14) +github.com/conformal/btcwire/msginv.go MsgInv.BtcDecode 100.00% (14/14) +github.com/conformal/btcwire/msgnotfound.go MsgNotFound.BtcDecode 100.00% (14/14) +github.com/conformal/btcwire/msgaddr.go MsgAddr.BtcDecode 100.00% (14/14) github.com/conformal/btcwire/msgtx.go readTxOut 100.00% (12/12) github.com/conformal/btcwire/msginv.go MsgInv.BtcEncode 100.00% (12/12) github.com/conformal/btcwire/protocol.go ServiceFlag.String 100.00% (12/12) github.com/conformal/btcwire/msggetdata.go MsgGetData.BtcEncode 100.00% (12/12) github.com/conformal/btcwire/msgnotfound.go MsgNotFound.BtcEncode 100.00% (12/12) github.com/conformal/btcwire/msgtx.go writeTxOut 100.00% (11/11) -github.com/conformal/btcwire/msgblock.go MsgBlock.BtcDecode 100.00% (10/10) +github.com/conformal/btcwire/msgblock.go MsgBlock.BtcDecode 100.00% (11/11) github.com/conformal/btcwire/blockheader.go readBlockHeader 100.00% (10/10) github.com/conformal/btcwire/message.go discardInput 100.00% (10/10) github.com/conformal/btcwire/msgblock.go MsgBlock.BtcEncode 100.00% (9/9) github.com/conformal/btcwire/blockheader.go writeBlockHeader 100.00% (8/8) -github.com/conformal/btcwire/common.go readVarString 100.00% (8/8) -github.com/conformal/btcwire/msgalert.go MsgAlert.BtcEncode 100.00% (8/8) github.com/conformal/btcwire/msgalert.go MsgAlert.BtcDecode 100.00% (8/8) +github.com/conformal/btcwire/msgalert.go MsgAlert.BtcEncode 100.00% (8/8) +github.com/conformal/btcwire/common.go readVarString 100.00% (8/8) github.com/conformal/btcwire/common.go writeVarString 100.00% (7/7) -github.com/conformal/btcwire/common.go randomUint64 100.00% (7/7) github.com/conformal/btcwire/msgpong.go MsgPong.BtcEncode 100.00% (7/7) github.com/conformal/btcwire/msgversion.go NewMsgVersionFromConn 100.00% (7/7) +github.com/conformal/btcwire/common.go randomUint64 100.00% (7/7) github.com/conformal/btcwire/msgpong.go MsgPong.BtcDecode 100.00% (7/7) github.com/conformal/btcwire/message.go readMessageHeader 100.00% (7/7) github.com/conformal/btcwire/common.go DoubleSha256 100.00% (7/7) @@ -154,5 +154,5 @@ github.com/conformal/btcwire/msggetaddr.go MsgGetAddr.BtcEncode 100.00% (1/1 github.com/conformal/btcwire/msginv.go MsgInv.Command 100.00% (1/1) github.com/conformal/btcwire/msginv.go MsgInv.MaxPayloadLength 100.00% (1/1) github.com/conformal/btcwire/msginv.go NewMsgInv 100.00% (1/1) -github.com/conformal/btcwire --------------------------------- 100.00% (932/932) +github.com/conformal/btcwire --------------------------------- 100.00% (943/943)