btcd/wire/msgmerkleblock.go

167 lines
4.9 KiB
Go
Raw Permalink Normal View History

// Copyright (c) 2014-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package wire
import (
"fmt"
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// maxFlagsPerMerkleBlock is the maximum number of flag bytes that could
// possibly fit into a merkle block. Since each transaction is represented by
// a single bit, this is the max number of transactions per block divided by
// 8 bits per byte. Then an extra one to cover partials.
const maxFlagsPerMerkleBlock = maxTxPerBlock / 8
// MsgMerkleBlock implements the Message interface and represents a bitcoin
// merkleblock message which is used to reset a Bloom filter.
//
// This message was not added until protocol version BIP0037Version.
type MsgMerkleBlock struct {
Header BlockHeader
Transactions uint32
Hashes []*chainhash.Hash
Flags []byte
}
// AddTxHash adds a new transaction hash to the message.
func (msg *MsgMerkleBlock) AddTxHash(hash *chainhash.Hash) error {
if len(msg.Hashes)+1 > maxTxPerBlock {
str := fmt.Sprintf("too many tx hashes for message [max %v]",
maxTxPerBlock)
return messageError("MsgMerkleBlock.AddTxHash", str)
}
msg.Hashes = append(msg.Hashes, hash)
return nil
}
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
// This is part of the Message interface implementation.
func (msg *MsgMerkleBlock) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error {
if pver < BIP0037Version {
str := fmt.Sprintf("merkleblock message invalid for protocol "+
"version %d", pver)
return messageError("MsgMerkleBlock.BtcDecode", str)
}
buf := binarySerializer.Borrow()
defer binarySerializer.Return(buf)
err := readBlockHeaderBuf(r, pver, &msg.Header, buf)
if err != nil {
return err
}
if _, err := io.ReadFull(r, buf[:4]); err != nil {
return err
}
msg.Transactions = littleEndian.Uint32(buf[:4])
// Read num block locator hashes and limit to max.
count, err := ReadVarIntBuf(r, pver, buf)
if err != nil {
return err
}
if count > maxTxPerBlock {
str := fmt.Sprintf("too many transaction hashes for message "+
"[count %v, max %v]", count, maxTxPerBlock)
return messageError("MsgMerkleBlock.BtcDecode", str)
}
wire: Reduce allocs with contiguous slices. The current code involves a ton of small allocations which is harsh on the garbage collector and in turn causes a lot of addition runtime overhead both in terms of additional memory and processing time. In order to improve the situation, this drasticially reduces the number of allocations by creating contiguous slices of objects and deserializing into them. Since the final data structures consist of slices of pointers to the objects, they are constructed by pointing them into the appropriate offset of the contiguous slice. This could be improved upon even further by converting all of the data structures provided the wire package to be slices of contiguous objects directly, however that would be a major breaking API change and would end up requiring updating a lot more code in every caller. I do think that ultimately the API should be changed, but the changes in this commit already makes a massive difference and it doesn't require touching any of the callers, so it is a good place to begin. 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 ----------------------------------------------------------- DeserializeTxLarge 16715 11146 -33.32% DecodeGetHeaders 501 2 -99.60% DecodeHeaders 2001 2 -99.90% DecodeGetBlocks 501 2 -99.60% DecodeAddr 3001 2002 -33.29% DecodeInv 50003 3 -99.99% DecodeNotFound 50002 3 -99.99% DecodeMerkleBlock 107 3 -97.20%
2016-04-21 23:49:38 +02:00
// Create a contiguous slice of hashes to deserialize into in order to
// reduce the number of allocations.
hashes := make([]chainhash.Hash, count)
msg.Hashes = make([]*chainhash.Hash, 0, count)
for i := uint64(0); i < count; i++ {
wire: Reduce allocs with contiguous slices. The current code involves a ton of small allocations which is harsh on the garbage collector and in turn causes a lot of addition runtime overhead both in terms of additional memory and processing time. In order to improve the situation, this drasticially reduces the number of allocations by creating contiguous slices of objects and deserializing into them. Since the final data structures consist of slices of pointers to the objects, they are constructed by pointing them into the appropriate offset of the contiguous slice. This could be improved upon even further by converting all of the data structures provided the wire package to be slices of contiguous objects directly, however that would be a major breaking API change and would end up requiring updating a lot more code in every caller. I do think that ultimately the API should be changed, but the changes in this commit already makes a massive difference and it doesn't require touching any of the callers, so it is a good place to begin. 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 ----------------------------------------------------------- DeserializeTxLarge 16715 11146 -33.32% DecodeGetHeaders 501 2 -99.60% DecodeHeaders 2001 2 -99.90% DecodeGetBlocks 501 2 -99.60% DecodeAddr 3001 2002 -33.29% DecodeInv 50003 3 -99.99% DecodeNotFound 50002 3 -99.99% DecodeMerkleBlock 107 3 -97.20%
2016-04-21 23:49:38 +02:00
hash := &hashes[i]
_, err := io.ReadFull(r, hash[:])
if err != nil {
return err
}
wire: Reduce allocs with contiguous slices. The current code involves a ton of small allocations which is harsh on the garbage collector and in turn causes a lot of addition runtime overhead both in terms of additional memory and processing time. In order to improve the situation, this drasticially reduces the number of allocations by creating contiguous slices of objects and deserializing into them. Since the final data structures consist of slices of pointers to the objects, they are constructed by pointing them into the appropriate offset of the contiguous slice. This could be improved upon even further by converting all of the data structures provided the wire package to be slices of contiguous objects directly, however that would be a major breaking API change and would end up requiring updating a lot more code in every caller. I do think that ultimately the API should be changed, but the changes in this commit already makes a massive difference and it doesn't require touching any of the callers, so it is a good place to begin. 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 ----------------------------------------------------------- DeserializeTxLarge 16715 11146 -33.32% DecodeGetHeaders 501 2 -99.60% DecodeHeaders 2001 2 -99.90% DecodeGetBlocks 501 2 -99.60% DecodeAddr 3001 2002 -33.29% DecodeInv 50003 3 -99.99% DecodeNotFound 50002 3 -99.99% DecodeMerkleBlock 107 3 -97.20%
2016-04-21 23:49:38 +02:00
msg.AddTxHash(hash)
}
msg.Flags, err = ReadVarBytesBuf(r, pver, buf, maxFlagsPerMerkleBlock,
"merkle block flags size")
return err
}
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
// This is part of the Message interface implementation.
func (msg *MsgMerkleBlock) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding) error {
if pver < BIP0037Version {
str := fmt.Sprintf("merkleblock message invalid for protocol "+
"version %d", pver)
return messageError("MsgMerkleBlock.BtcEncode", str)
}
// Read num transaction hashes and limit to max.
numHashes := len(msg.Hashes)
if numHashes > maxTxPerBlock {
str := fmt.Sprintf("too many transaction hashes for message "+
"[count %v, max %v]", numHashes, maxTxPerBlock)
return messageError("MsgMerkleBlock.BtcDecode", str)
}
numFlagBytes := len(msg.Flags)
if numFlagBytes > maxFlagsPerMerkleBlock {
str := fmt.Sprintf("too many flag bytes for message [count %v, "+
"max %v]", numFlagBytes, maxFlagsPerMerkleBlock)
return messageError("MsgMerkleBlock.BtcDecode", str)
}
buf := binarySerializer.Borrow()
defer binarySerializer.Return(buf)
err := writeBlockHeaderBuf(w, pver, &msg.Header, buf)
if err != nil {
return err
}
littleEndian.PutUint32(buf[:4], msg.Transactions)
if _, err := w.Write(buf[:4]); err != nil {
return err
}
err = WriteVarIntBuf(w, pver, uint64(numHashes), buf)
if err != nil {
return err
}
for _, hash := range msg.Hashes {
_, err := w.Write(hash[:])
if err != nil {
return err
}
}
err = WriteVarBytesBuf(w, pver, msg.Flags, buf)
return err
}
// Command returns the protocol command string for the message. This is part
// of the Message interface implementation.
func (msg *MsgMerkleBlock) Command() string {
return CmdMerkleBlock
}
// MaxPayloadLength returns the maximum length the payload can be for the
// receiver. This is part of the Message interface implementation.
func (msg *MsgMerkleBlock) MaxPayloadLength(pver uint32) uint32 {
return MaxBlockPayload
}
// NewMsgMerkleBlock returns a new bitcoin merkleblock message that conforms to
// the Message interface. See MsgMerkleBlock for details.
func NewMsgMerkleBlock(bh *BlockHeader) *MsgMerkleBlock {
return &MsgMerkleBlock{
Header: *bh,
Transactions: 0,
Hashes: make([]*chainhash.Hash, 0),
Flags: make([]byte, 0),
}
}