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%
This commit is contained in:
Dave Collins 2016-04-21 16:49:38 -05:00
parent 5de5b7354c
commit 2adfb3b56a
9 changed files with 56 additions and 30 deletions

View file

@ -70,14 +70,15 @@ func (msg *MsgAddr) BtcDecode(r io.Reader, pver uint32) error {
return messageError("MsgAddr.BtcDecode", str) return messageError("MsgAddr.BtcDecode", str)
} }
addrList := make([]NetAddress, count)
msg.AddrList = make([]*NetAddress, 0, count) msg.AddrList = make([]*NetAddress, 0, count)
for i := uint64(0); i < count; i++ { for i := uint64(0); i < count; i++ {
na := NetAddress{} na := &addrList[i]
err := readNetAddress(r, pver, &na, true) err := readNetAddress(r, pver, na, true)
if err != nil { if err != nil {
return err return err
} }
msg.AddAddress(&na) msg.AddAddress(na)
} }
return nil return nil
} }

View file

@ -65,14 +65,17 @@ func (msg *MsgGetBlocks) BtcDecode(r io.Reader, pver uint32) error {
return messageError("MsgGetBlocks.BtcDecode", str) 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) msg.BlockLocatorHashes = make([]*ShaHash, 0, count)
for i := uint64(0); i < count; i++ { for i := uint64(0); i < count; i++ {
sha := ShaHash{} hash := &locatorHashes[i]
err := readElement(r, &sha) err := readElement(r, hash)
if err != nil { if err != nil {
return err return err
} }
msg.AddBlockLocatorHash(&sha) msg.AddBlockLocatorHash(hash)
} }
err = readElement(r, &msg.HashStop) err = readElement(r, &msg.HashStop)

View file

@ -49,14 +49,17 @@ func (msg *MsgGetData) BtcDecode(r io.Reader, pver uint32) error {
return messageError("MsgGetData.BtcDecode", str) 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) msg.InvList = make([]*InvVect, 0, count)
for i := uint64(0); i < count; i++ { for i := uint64(0); i < count; i++ {
iv := InvVect{} iv := &invList[i]
err := readInvVect(r, pver, &iv) err := readInvVect(r, pver, iv)
if err != nil { if err != nil {
return err return err
} }
msg.AddInvVect(&iv) msg.AddInvVect(iv)
} }
return nil return nil

View file

@ -62,14 +62,17 @@ func (msg *MsgGetHeaders) BtcDecode(r io.Reader, pver uint32) error {
return messageError("MsgGetHeaders.BtcDecode", str) 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) msg.BlockLocatorHashes = make([]*ShaHash, 0, count)
for i := uint64(0); i < count; i++ { for i := uint64(0); i < count; i++ {
sha := ShaHash{} hash := &locatorHashes[i]
err := readElement(r, &sha) err := readElement(r, hash)
if err != nil { if err != nil {
return err return err
} }
msg.AddBlockLocatorHash(&sha) msg.AddBlockLocatorHash(hash)
} }
err = readElement(r, &msg.HashStop) err = readElement(r, &msg.HashStop)

View file

@ -49,10 +49,13 @@ func (msg *MsgHeaders) BtcDecode(r io.Reader, pver uint32) error {
return messageError("MsgHeaders.BtcDecode", str) 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) msg.Headers = make([]*BlockHeader, 0, count)
for i := uint64(0); i < count; i++ { for i := uint64(0); i < count; i++ {
bh := BlockHeader{} bh := &headers[i]
err := readBlockHeader(r, pver, &bh) err := readBlockHeader(r, pver, bh)
if err != nil { if err != nil {
return err return err
} }
@ -68,7 +71,7 @@ func (msg *MsgHeaders) BtcDecode(r io.Reader, pver uint32) error {
"transactions [count %v]", txCount) "transactions [count %v]", txCount)
return messageError("MsgHeaders.BtcDecode", str) return messageError("MsgHeaders.BtcDecode", str)
} }
msg.AddBlockHeader(&bh) msg.AddBlockHeader(bh)
} }
return nil return nil

View file

@ -57,14 +57,17 @@ func (msg *MsgInv) BtcDecode(r io.Reader, pver uint32) error {
return messageError("MsgInv.BtcDecode", str) 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) msg.InvList = make([]*InvVect, 0, count)
for i := uint64(0); i < count; i++ { for i := uint64(0); i < count; i++ {
iv := InvVect{} iv := &invList[i]
err := readInvVect(r, pver, &iv) err := readInvVect(r, pver, iv)
if err != nil { if err != nil {
return err return err
} }
msg.AddInvVect(&iv) msg.AddInvVect(iv)
} }
return nil return nil

View file

@ -68,14 +68,17 @@ func (msg *MsgMerkleBlock) BtcDecode(r io.Reader, pver uint32) error {
return messageError("MsgMerkleBlock.BtcDecode", str) 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) msg.Hashes = make([]*ShaHash, 0, count)
for i := uint64(0); i < count; i++ { for i := uint64(0); i < count; i++ {
var sha ShaHash hash := &hashes[i]
err := readElement(r, &sha) err := readElement(r, hash)
if err != nil { if err != nil {
return err return err
} }
msg.AddTxHash(&sha) msg.AddTxHash(hash)
} }
msg.Flags, err = ReadVarBytes(r, pver, maxFlagsPerMerkleBlock, msg.Flags, err = ReadVarBytes(r, pver, maxFlagsPerMerkleBlock,

View file

@ -46,14 +46,17 @@ func (msg *MsgNotFound) BtcDecode(r io.Reader, pver uint32) error {
return messageError("MsgNotFound.BtcDecode", str) 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) msg.InvList = make([]*InvVect, 0, count)
for i := uint64(0); i < count; i++ { for i := uint64(0); i < count; i++ {
iv := InvVect{} iv := &invList[i]
err := readInvVect(r, pver, &iv) err := readInvVect(r, pver, iv)
if err != nil { if err != nil {
return err return err
} }
msg.AddInvVect(&iv) msg.AddInvVect(iv)
} }
return nil return nil

View file

@ -263,14 +263,16 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32) error {
return messageError("MsgTx.BtcDecode", str) return messageError("MsgTx.BtcDecode", str)
} }
// Deserialize the inputs.
txIns := make([]TxIn, count)
msg.TxIn = make([]*TxIn, count) msg.TxIn = make([]*TxIn, count)
for i := uint64(0); i < count; i++ { for i := uint64(0); i < count; i++ {
ti := TxIn{} ti := &txIns[i]
err = readTxIn(r, pver, msg.Version, &ti) err = readTxIn(r, pver, msg.Version, ti)
if err != nil { if err != nil {
return err return err
} }
msg.TxIn[i] = &ti msg.TxIn[i] = ti
} }
count, err = ReadVarInt(r, pver) count, err = ReadVarInt(r, pver)
@ -288,14 +290,16 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32) error {
return messageError("MsgTx.BtcDecode", str) return messageError("MsgTx.BtcDecode", str)
} }
// Deserialize the outputs.
txOuts := make([]TxOut, count)
msg.TxOut = make([]*TxOut, count) msg.TxOut = make([]*TxOut, count)
for i := uint64(0); i < count; i++ { for i := uint64(0); i < count; i++ {
to := TxOut{} to := &txOuts[i]
err = readTxOut(r, pver, msg.Version, &to) err = readTxOut(r, pver, msg.Version, to)
if err != nil { if err != nil {
return err return err
} }
msg.TxOut[i] = &to msg.TxOut[i] = to
} }
msg.LockTime, err = binarySerializer.Uint32(r, littleEndian) msg.LockTime, err = binarySerializer.Uint32(r, littleEndian)