Skip to content

Conversation

joaopaulofonseca
Copy link
Contributor

@joaopaulofonseca joaopaulofonseca commented Mar 7, 2017

This PR tries to improve the performance of the listunspent, fundrawtransaction, sendtoaddress and other RPC calls by improving CWallet::AvailableCoins() iteration through the wallet transactions.

E.g. If a given address is an outpoint of N transactions, I guess there's no point to check N times if the address IsMine().

This PR supersedes #7823 although it's a different approach. In that PR, caching accurately those values was a little tricky because of all the scenarios that could make the cache invalidated (conflicts, double-spends, reorganizations, abandoned transactions, etc, ...).

Still, with this approach I got performance gains up to 90% on CWallet::AvailableCoins() for large wallets.

mine = mapOutputIsMine[pcoin->tx->vout[i].scriptPubKey] = IsMine(pcoin->tx->vout[i]);
}

if (mine != ISMINE_NO && !(IsSpent(wtxid, i)) &&
Copy link
Contributor

@pedrobranco pedrobranco Mar 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I there's an ACK from the community, I would love to see this giant if break down with several early continues (to facilitate the reading), as applied here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would seem like a good idea, especially if the continues were documented with new comments. Not really on-topic for this PR, however.

@NicolasDorier
Copy link
Contributor

Please check #9167, morcos is working on it

@joaopaulofonseca
Copy link
Contributor Author

@pedrobranco yes, that if is a little mess. I'll also take a look at it.

@NicolasDorier If I understood the @morcos PR correctly, it's related to the isminetype of the transaction inputs and not the outputs, which we need here to find our utxos.

@NicolasDorier
Copy link
Contributor

my bad I read too fast.

@NicolasDorier
Copy link
Contributor

Why not having the cache scoped to the WalletTransaction type ?
Instead of

IsMine(pcoin->tx->vout[i])

we would have

pcoin->IsMine(i)

where WalletTransaction::IsMine would cache the result at the WalletTransaction level.

  1. this would make it more readable,
  2. this cache could be populated everywhere in the code when IsMine is called,
  3. the cache would span accross different calls to AvailableCoins, making AvailableCoins even faster.

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK dca8d8926e655ba6639e748bcd32c560a5539b88

Code seems fine, but I don't know very much about the performance implications of this change, and would leave it for others to discuss. NicolasDorier's suggestion seems like a reasonable alternative, too.

mine = im->second;
} else {
mine = mapOutputIsMine[pcoin->tx->vout[i].scriptPubKey] = IsMine(pcoin->tx->vout[i]);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can simplify this as:

+                auto inserted = mapOutputIsMine.emplace(pcoin->tx->vout[i].scriptPubKey, ISMINE_NO);
+                if (inserted.second) {
+                   inserted.first->second = IsMine(pcoin->tx->vout[i]);
+                }
+                isminetype mine = inserted.first->second;

mine = mapOutputIsMine[pcoin->tx->vout[i].scriptPubKey] = IsMine(pcoin->tx->vout[i]);
}

if (mine != ISMINE_NO && !(IsSpent(wtxid, i)) &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would seem like a good idea, especially if the continues were documented with new comments. Not really on-topic for this PR, however.

@joaopaulofonseca
Copy link
Contributor Author

joaopaulofonseca commented Mar 10, 2017

@NicolasDorier that's a good suggestion. Actually a while ago I was trying to create a cache to store these values, but at the wallet level. But couldn't manage to successfully achieve a solution with both reliability and speed. But I will look into your approach.

@ryanofsky Ok, I like it. I'll simplify that piece of code.

@joaopaulofonseca joaopaulofonseca force-pushed the enhancement/cache-wallet-available-coins-output-ismine branch from dca8d89 to 46cbc0a Compare March 13, 2017 17:30
@RHavar
Copy link
Contributor

RHavar commented Jun 1, 2017

I've tested 46cbc0a

Before median time of listunspent after a few runs on my wallet was 14.4 seconds, now it's 12.8s.

So it seems faster, but not earth shatteringly so. For reference my wallet has about ~520k transactions in it, where of them ~168k are mine (outgoing transactions).

--

To me it seems like @NicolasDorier's suggestion is the way to go, I suspect the reuse of the cache is going to help a lot.

@laanwj
Copy link
Member

laanwj commented Jun 5, 2017

@RHavar thanks for testing!

@joaopaulofonseca
Copy link
Contributor Author

joaopaulofonseca commented Jun 5, 2017

@RHavar thanks a lot for your feedback! The current implementation of listunspent is a little tricky to optimize without a larger refactor.

I've tried to optimize each of the two heavier calls within listunspent: IsMine() and IsSpent(). But without optimizing both, they will only fix different use cases separately, which will behave very differently for large wallet vs large amount of unspents.

I haven't had much time to work on this lately, but it's a problem I'd like to come up with a clean solution in the near future, since it's an issue affecting almost everyone with large wallets.

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK 46cbc0a. Code is slightly simplified since last review, and change looks good assuming performance improvements warrant it.

@NicolasDorier
Copy link
Contributor

utACK 46cbc0a, it is simple enough. Can keep wallet level caching for later PR.

@ryanofsky
Copy link
Contributor

Change is simple and has multiple acks, but needs to be rebased.

@maflcko
Copy link
Member

maflcko commented Nov 10, 2017

Not rebased in 30 days.

This is now up for grabs.

@maflcko maflcko closed this Nov 10, 2017
@joaopaulofonseca
Copy link
Contributor Author

Sorry guys, haven't had much time to work on this lately.. Although this improves the speed, the last tests I did showed there are still some edge cases where the unspent count differs which means that the cache is not accurate.. I'll try to pick this up in the future, and reopen if needed.

@ryanofsky
Copy link
Contributor

Could add up for grabs label

giaki3003 added a commit to LUX-Core/lux that referenced this pull request May 2, 2019
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Sep 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants