From 2adfb3b56acd280e84451e94dd0c06203eef9832 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Thu, 21 Apr 2016 16:49:38 -0500 Subject: [PATCH] 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% --- wire/msgaddr.go | 7 ++++--- wire/msggetblocks.go | 9 ++++++--- wire/msggetdata.go | 9 ++++++--- wire/msggetheaders.go | 9 ++++++--- wire/msgheaders.go | 9 ++++++--- wire/msginv.go | 9 ++++++--- wire/msgmerkleblock.go | 9 ++++++--- wire/msgnotfound.go | 9 ++++++--- wire/msgtx.go | 16 ++++++++++------ 9 files changed, 56 insertions(+), 30 deletions(-) diff --git a/wire/msgaddr.go b/wire/msgaddr.go index 2c40e4d4..f64f9267 100644 --- a/wire/msgaddr.go +++ b/wire/msgaddr.go @@ -70,14 +70,15 @@ func (msg *MsgAddr) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgAddr.BtcDecode", str) } + addrList := make([]NetAddress, count) msg.AddrList = make([]*NetAddress, 0, count) for i := uint64(0); i < count; i++ { - na := NetAddress{} - err := readNetAddress(r, pver, &na, true) + na := &addrList[i] + err := readNetAddress(r, pver, na, true) if err != nil { return err } - msg.AddAddress(&na) + msg.AddAddress(na) } return nil } diff --git a/wire/msggetblocks.go b/wire/msggetblocks.go index 4d8d5b58..d1fa9a6e 100644 --- a/wire/msggetblocks.go +++ b/wire/msggetblocks.go @@ -65,14 +65,17 @@ func (msg *MsgGetBlocks) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgGetBlocks.BtcDecode", str) } + // Create a contiguous slice of hashes to deserialize into in order to + // reduce the number of allocations. + locatorHashes := make([]ShaHash, count) msg.BlockLocatorHashes = make([]*ShaHash, 0, count) for i := uint64(0); i < count; i++ { - sha := ShaHash{} - err := readElement(r, &sha) + hash := &locatorHashes[i] + err := readElement(r, hash) if err != nil { return err } - msg.AddBlockLocatorHash(&sha) + msg.AddBlockLocatorHash(hash) } err = readElement(r, &msg.HashStop) diff --git a/wire/msggetdata.go b/wire/msggetdata.go index 2cc58b03..b646c46c 100644 --- a/wire/msggetdata.go +++ b/wire/msggetdata.go @@ -49,14 +49,17 @@ func (msg *MsgGetData) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgGetData.BtcDecode", str) } + // Create a contiguous slice of inventory vectors to deserialize into in + // order to reduce the number of allocations. + invList := make([]InvVect, count) msg.InvList = make([]*InvVect, 0, count) for i := uint64(0); i < count; i++ { - iv := InvVect{} - err := readInvVect(r, pver, &iv) + iv := &invList[i] + err := readInvVect(r, pver, iv) if err != nil { return err } - msg.AddInvVect(&iv) + msg.AddInvVect(iv) } return nil diff --git a/wire/msggetheaders.go b/wire/msggetheaders.go index 6d56560b..dd70e0d0 100644 --- a/wire/msggetheaders.go +++ b/wire/msggetheaders.go @@ -62,14 +62,17 @@ func (msg *MsgGetHeaders) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgGetHeaders.BtcDecode", str) } + // Create a contiguous slice of hashes to deserialize into in order to + // reduce the number of allocations. + locatorHashes := make([]ShaHash, count) msg.BlockLocatorHashes = make([]*ShaHash, 0, count) for i := uint64(0); i < count; i++ { - sha := ShaHash{} - err := readElement(r, &sha) + hash := &locatorHashes[i] + err := readElement(r, hash) if err != nil { return err } - msg.AddBlockLocatorHash(&sha) + msg.AddBlockLocatorHash(hash) } err = readElement(r, &msg.HashStop) diff --git a/wire/msgheaders.go b/wire/msgheaders.go index 62b60768..588b8be5 100644 --- a/wire/msgheaders.go +++ b/wire/msgheaders.go @@ -49,10 +49,13 @@ func (msg *MsgHeaders) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgHeaders.BtcDecode", str) } + // Create a contiguous slice of headers to deserialize into in order to + // reduce the number of allocations. + headers := make([]BlockHeader, count) msg.Headers = make([]*BlockHeader, 0, count) for i := uint64(0); i < count; i++ { - bh := BlockHeader{} - err := readBlockHeader(r, pver, &bh) + bh := &headers[i] + err := readBlockHeader(r, pver, bh) if err != nil { return err } @@ -68,7 +71,7 @@ func (msg *MsgHeaders) BtcDecode(r io.Reader, pver uint32) error { "transactions [count %v]", txCount) return messageError("MsgHeaders.BtcDecode", str) } - msg.AddBlockHeader(&bh) + msg.AddBlockHeader(bh) } return nil diff --git a/wire/msginv.go b/wire/msginv.go index 905531ae..34db4bb0 100644 --- a/wire/msginv.go +++ b/wire/msginv.go @@ -57,14 +57,17 @@ func (msg *MsgInv) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgInv.BtcDecode", str) } + // Create a contiguous slice of inventory vectors to deserialize into in + // order to reduce the number of allocations. + invList := make([]InvVect, count) msg.InvList = make([]*InvVect, 0, count) for i := uint64(0); i < count; i++ { - iv := InvVect{} - err := readInvVect(r, pver, &iv) + iv := &invList[i] + err := readInvVect(r, pver, iv) if err != nil { return err } - msg.AddInvVect(&iv) + msg.AddInvVect(iv) } return nil diff --git a/wire/msgmerkleblock.go b/wire/msgmerkleblock.go index 7dd099d7..96be6725 100644 --- a/wire/msgmerkleblock.go +++ b/wire/msgmerkleblock.go @@ -68,14 +68,17 @@ func (msg *MsgMerkleBlock) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgMerkleBlock.BtcDecode", str) } + // Create a contiguous slice of hashes to deserialize into in order to + // reduce the number of allocations. + hashes := make([]ShaHash, count) msg.Hashes = make([]*ShaHash, 0, count) for i := uint64(0); i < count; i++ { - var sha ShaHash - err := readElement(r, &sha) + hash := &hashes[i] + err := readElement(r, hash) if err != nil { return err } - msg.AddTxHash(&sha) + msg.AddTxHash(hash) } msg.Flags, err = ReadVarBytes(r, pver, maxFlagsPerMerkleBlock, diff --git a/wire/msgnotfound.go b/wire/msgnotfound.go index 31dddbae..0c95020d 100644 --- a/wire/msgnotfound.go +++ b/wire/msgnotfound.go @@ -46,14 +46,17 @@ func (msg *MsgNotFound) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgNotFound.BtcDecode", str) } + // Create a contiguous slice of inventory vectors to deserialize into in + // order to reduce the number of allocations. + invList := make([]InvVect, count) msg.InvList = make([]*InvVect, 0, count) for i := uint64(0); i < count; i++ { - iv := InvVect{} - err := readInvVect(r, pver, &iv) + iv := &invList[i] + err := readInvVect(r, pver, iv) if err != nil { return err } - msg.AddInvVect(&iv) + msg.AddInvVect(iv) } return nil diff --git a/wire/msgtx.go b/wire/msgtx.go index 43de4a74..eceddb7d 100644 --- a/wire/msgtx.go +++ b/wire/msgtx.go @@ -263,14 +263,16 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgTx.BtcDecode", str) } + // Deserialize the inputs. + txIns := make([]TxIn, count) msg.TxIn = make([]*TxIn, count) for i := uint64(0); i < count; i++ { - ti := TxIn{} - err = readTxIn(r, pver, msg.Version, &ti) + ti := &txIns[i] + err = readTxIn(r, pver, msg.Version, ti) if err != nil { return err } - msg.TxIn[i] = &ti + msg.TxIn[i] = ti } count, err = ReadVarInt(r, pver) @@ -288,14 +290,16 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32) error { return messageError("MsgTx.BtcDecode", str) } + // Deserialize the outputs. + txOuts := make([]TxOut, count) msg.TxOut = make([]*TxOut, count) for i := uint64(0); i < count; i++ { - to := TxOut{} - err = readTxOut(r, pver, msg.Version, &to) + to := &txOuts[i] + err = readTxOut(r, pver, msg.Version, to) if err != nil { return err } - msg.TxOut[i] = &to + msg.TxOut[i] = to } msg.LockTime, err = binarySerializer.Uint32(r, littleEndian)