While running my coin selection simulations, I noticed that towards the end of the simulation, the wallet would become slow to make new transactions. The wallet generally performs much more slowly when there are a large number of transactions and/or a large number of keys. The improvements here are focused on wallets with a large number of transactions as that is what the simulations produce.
Most of the slowdown I observed was due to DescriptorScriptPubKeyMan::GetSigningProvider
re-deriving keys every time it is called. To avoid this, it will now cache the SigningProvider
produced so that repeatedly fetching the SigningProvider
for the same script will not result in the same key being derived over and over. This has a side effect of making the function non-const, which makes a lot of other functions non-const as well. This helps with wallets with lots of address reuse (as my coin selection simulations are), but not if addresses are not reused as keys will end up needing to be derived the first time GetSigningProvider
is called for a script.
The GetSigningProvider
problem was also exacerbated by unnecessarily fetching a SigningProvider
for the same script multiple times. A SigningProvider
is retrieved to be used inside of IsSolvable
. A few lines later, we use GetTxSpendSize
which fetches a SigningProvider
and then calls CalculateMaximumSignedInputSize
. We can avoid a second call to GetSigningProvider
by using CalculateMaximumSignedInputSize
directly with the SigningProvider
already retrieved for IsSolvable
.
There is an additional slowdown where ProduceSignature
with a dummy signer is called twice for each output. The first time is IsSolvable
checks that ProduceSignature
succeeds, thereby informing whether we have solving data. The second is CalculateMaximumSignedInputSize
which returns -1 if ProduceSignature
fails, and returns the input size otherwise. We can reduce this to one call of ProduceSignature
by using CalculateMaximumSignedInputSize
’s result to set solvable
.
Lastly, a lot of time is spent looking in mapWallet
and mapTxSpends
to determine whether an output is already spent. The performance of these lookups is slightly improved by changing those maps to use std::unordered_map
and std::unordered_multimap
respectively.