From 6b802379ec38f3b40f5ecbf2ee04075d02aa788a Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Tue, 22 Aug 2017 20:30:18 -0500 Subject: [PATCH] txscript: Shallow tx copy for signature hash calc. This modifies calcSignatureHash to use a shallow copy of the transaction versus a deep copy since the actual scripts themselves are not modified and therefore don't need to be copied. This is being done because profiling the most overall allocated space shows that the deep copy performed in calcSignatureHash accounts for nearly 20% of all allocations on a synced running instance. Also, copying all of the additional data makes it more time consuming as well. With this change, that figure drops from ~20% to ~5% of all allocations. The following benchmark shows the relative speedups and allocation reduction as a result of the optimization on my system. In particular, the changes result in approximately a 15% speedup and a whopping 99.89% reduction in allocations when using a large transaction with thousands of inputs which was the worst case scenario. benchmark old allocs new allocs delta -------------------------------------------------------------------- BenchmarkCalcSignatureHash 11151 12 -99.89% benchmark old ns/op new ns/op delta -------------------------------------------------------------------- BenchmarkCalcSignatureHash 3599845 3056359 -15.10% --- txscript/script.go | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 8d246092..1c3939b0 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -554,6 +554,34 @@ func CalcWitnessSigHash(script []byte, sigHashes *TxSigHashes, hType SigHashType amt) } +// shallowCopyTx creates a shallow copy of the transaction for use when +// calculating the signature hash. It is used over the Copy method on the +// transaction itself since that is a deep copy and therefore does more work and +// allocates much more space than needed. +func shallowCopyTx(tx *wire.MsgTx) wire.MsgTx { + // As an additional memory optimization, use contiguous backing arrays + // for the copied inputs and outputs and point the final slice of + // pointers into the contiguous arrays. This avoids a lot of small + // allocations. + txCopy := wire.MsgTx{ + Version: tx.Version, + TxIn: make([]*wire.TxIn, len(tx.TxIn)), + TxOut: make([]*wire.TxOut, len(tx.TxOut)), + LockTime: tx.LockTime, + } + txIns := make([]wire.TxIn, len(tx.TxIn)) + for i, oldTxIn := range tx.TxIn { + txIns[i] = *oldTxIn + txCopy.TxIn[i] = &txIns[i] + } + txOuts := make([]wire.TxOut, len(tx.TxOut)) + for i, oldTxOut := range tx.TxOut { + txOuts[i] = *oldTxOut + txCopy.TxOut[i] = &txOuts[i] + } + return txCopy +} + // calcSignatureHash will, given a script and hash type for the current script // engine instance, calculate the signature hash to be used for signing and // verification. @@ -587,9 +615,9 @@ func calcSignatureHash(script []parsedOpcode, hashType SigHashType, tx *wire.Msg // Remove all instances of OP_CODESEPARATOR from the script. script = removeOpcode(script, OP_CODESEPARATOR) - // Make a deep copy of the transaction, zeroing out the script for all - // inputs that are not currently being processed. - txCopy := tx.Copy() + // Make a shallow copy of the transaction, zeroing out the script for + // all inputs that are not currently being processed. + txCopy := shallowCopyTx(tx) for i := range txCopy.TxIn { if i == idx { // UnparseScript cannot fail here because removeOpcode