-
Notifications
You must be signed in to change notification settings - Fork 38.6k
txmempool -/-> validation 1/2: improve performance of check() and remove dependency on validation #23157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
bench results on master:
results on top of this PR:
(~4x faster) |
|
Sorry for being off-topic, but just because I always twitch slightly when I see it: The bench output is already formatted in Markdown extended syntax, so if you just paste it into the comment box, without backticks around it (and without leading whitespace), it renders a bit nicer. And it's easier to do, because less steps :-)
|
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
theStack
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK
jnewbery
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK 4bc0c8066e79f0d95053bcc2449a7d354eaa9c25
I've left a few nitty style comments inline.
I found the ordering of the commits:
- [mempool] speed up check by using coins cache and iterating in topo order
- [mempool] assert that check() always goes in topological order
- [mempool] remove code needed for unsorted mempool checking
to be a little bit confusing. In the intermediate commits, you have things like:
assert(mempoolDuplicate.HaveCoin(txin.prevout));
if (!mempoolDuplicate.HaveCoin(txin.prevout)) fDependsWait = true;
which you then remove in the subsequent commit. It's fine, but it's a bit more mental overhead for reviewers.
I think a more intuitive progression of commits would be to squash those three commits into two:
- the first commit just changes the for loop to use
GetSortedDepthAndScore()and adds theprev_ancestor_countsanity check. - the second commit removes the
waitingOnDependantscode since the iteration is now in depth order.
Is the next step here to change check() to take a const CCoinsViewCache& and int64_t height and remove the dependency on validation entirely?
4bc0c80 to
a420bcb
Compare
|
@flack good tip, fixed @theStack @jnewbery thanks for your reviews, I think I've incorporated all suggestions :)
Yes I thought I had included that commit here but apparently I hadn't - last push added it. |
a420bcb to
6bb6ee4
Compare
It might actually be better to defer that commit until after #23173 so fewer changes are needed to net_processing. Entirely up to you. |
… order No behavior changes. Before, we're always adding transactions to the "check later" queue if they have any parents in the mempool. But there's no reason to do this if all of its inputs are already available from mempoolDuplicate. Instead, check for inputs, and only mark fDependsWait=true if the parents haven't been processed yet. Reduce the amount of "check later" transactions by looking at ancestors before descendants. Do this by iterating through them in ascending order by ancestor count. This works because a child will always have more in-mempool ancestors than its parent. We should never have any entries in the "check later" queue after this commit.
Remove variables used for keeping track of mempool transactions for which we haven't processed the parents yet. Since we're iterating in topological order now, they're always unused.
UpdateCoins is an unnecessary dependency on validation. All we need to do is add and remove coins to check inputs. We don't need the extra logic for checking coinbases and handling TxUndos. Also remove the wrapper function in validation.h which constructs a throwaway TxUndo object before calling UpdateCoins because it is now unused.
No transaction in the mempool should ever be a coinbase. Since mempoolDuplicate's backend is the chainstate coins view, it should always contain the coins available.
Removes check's dependency on validation.h
6bb6ee4 to
082c5bf
Compare
|
reACK 082c5bf |
| int childTxs = 800; | ||
| if (bench.complexityN() > 1) { | ||
| childTxs = static_cast<int>(bench.complexityN()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This could be simplified to:
int childTxs = bench.complexityN() > 1 ? static_cast<int>(bench.complexityN()) : 800;as it is on line 105
|
tACK 082c5bf Ran the bench a few times before and after the optimization: Before optimization refactor
After optimization refactor
Optimization increase in op/s93,323,779.84 / 12,305,977.78 = 7.5836 (758.36%) |
|
concept ACK before:
after:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tACK 082c5bf
Bench comparison
Optimized
| ns/op | op/s | err% | total | benchmark |
|---|---|---|---|---|
| 2.39 | 417,806,513.20 | 0.6% | 0.01 | MempoolCheck |
| 2.39 | 419,189,093.49 | 0.6% | 0.01 | MempoolCheck |
| 2.39 | 419,188,854.66 | 0.6% | 0.01 | MempoolCheck |
| 2.39 | 419,049,672.97 | 0.3% | 0.01 | MempoolCheck |
Non-Optimized
| ns/op | op/s | err% | total | benchmark |
|---|---|---|---|---|
| 12.61 | 79,317,536.11 | 6.8% | 0.01 | MempoolCheck |
| 12.20 | 81,995,745.32 | 9.2% | 0.01 | MempoolCheck |
| 12.22 | 81,862,095.21 | 1.9% | 0.01 | MempoolCheck |
| 12.17 | 82,167,397.00 | 0.8% | 0.01 | MempoolCheck |
It seems I am observing only 6x speedup. But that's probably my local benching issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what's different or wrong on my system (running OpenBSD 6.9/amd64), but the benchmark doesn't show any noticable difference after the optimization in CTxMemPool::check, it even seems to be worse.
at commit 30e240f (with original check as on master branch):
$ ./src/bench/bench_bitcoin -filter=MempoolCheck
| ns/op | op/s | err% | total | benchmark |
|---|---|---|---|---|
| 100.66 | 9,934,356.36 | 1.6% | 0.01 | MempoolCheck |
at commit 54c6f3c (with optimized check):
$ ./src/bench/bench_bitcoin -filter=MempoolCheck
| ns/op | op/s | err% | total | benchmark |
|---|---|---|---|---|
| 107.36 | 9,314,108.15 | 4.4% | 0.01 | MempoolCheck |
Will try on a Debian Linux machine later to compare.
// EDIT: It seems like the difference in benchmark results is almost only caused by the very last commit.
That sounds strange to me, but I guess it's plausible that the bench only sees a speedup because of the parameter change rather than ordered traversal. Even so, I'd want to remove all that unnecessary code... |
Agree, removing unnecessary code is always a good thing! I didn't intend to sound concept NACKy, this was just a hint that the posted benchmark results have to be taken with a grain of salt :) will likely code-review this PR within the next days. |
theStack
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code-review ACK 082c5bf
mjdietzx
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Post merge ACK 082c5bf
Warming up to review some of your open PRs. Just one question / possible nit. You are KILLING it with these PRs, well done 💪
| assert(it->GetSizeWithAncestors() == nSizeCheck); | ||
| assert(it->GetSigOpCostWithAncestors() == nSigOpCheck); | ||
| assert(it->GetModFeesWithAncestors() == nFeesCheck); | ||
| // Sanity check: we are walking in ascending ancestor count order. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this sanity check necessary? It's not like GetSortedDepthAndScore() is complicated or has non-deterministic behavior, right?
And why do we really need to fail if somehow we do encounter this case? The only down-side I see is marking this as a "check later" transaction which is just a minor performance hit?
Are there any race conditions where we could possibly see this assert throw?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, I'm reviewing commit by commit, and now in e8639ec I am starting to understand why we have this assert.
But, I'm still wondering, why have this assert if assert(mempoolDuplicate.HaveCoin(txin.prevout)); will take care of the job should we run into any problems? I still don't see why this is beneficial
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not strictly necessary, no. It's just a relatively cheap-to-run sanity check. It can make things easier to debug if we have any problems, but yes it can be removed and this code would still be checking the same things.
Summary: This is a backport of [[bitcoin/bitcoin#23157 | core#23157]] [1 & 2/8] bitcoin/bitcoin@cb14071 bitcoin/bitcoin@30e240f Test Plan: `ninja bench-bitcoin` | ns/op | op/s | err% | total | benchmark |--------------------|--------------------|--------|----------|---------- | 172,478,220.00 | 5.80 | 0.1% | 1.90 | `ComplexMemPool` | ... | ... | ... | ... | ... | 8.07 | 123,977,164.61 | 0.0% | 0.00 | `MempoolCheck` Reviewers: #bitcoin_abc, Fabien Reviewed By: #bitcoin_abc, Fabien Subscribers: Fabien Differential Revision: https://reviews.bitcoinabc.org/D12169
Summary: > [mempool] speed up check() by using coins cache and iterating in topo order > > No behavior changes. > > Before, we're always adding transactions to the "check later" queue if > they have any parents in the mempool. But there's no reason to do this > if all of its inputs are already available from mempoolDuplicate. > Instead, check for inputs, and only mark fDependsWait=true if the > parents haven't been processed yet. > > Reduce the amount of "check later" transactions by looking at > ancestors before descendants. Do this by iterating through them in > ascending order by ancestor count. This works because a child will > always have more in-mempool ancestors than its parent. > > We should never have any entries in the "check later" queue > after this commit. bitcoin/bitcoin@54c6f3c > [mempool] remove now-unnecessary code > > Remove variables used for keeping track of mempool transactions for > which we haven't processed the parents yet. Since we're iterating in > topological order now, they're always unused. bitcoin/bitcoin@e8639ec This is a backport of [[bitcoin/bitcoin#23157 | core#23157]] [3 & 4/8] Depends on D12169 Test Plan: `ninja all check-all bench-bitcoin` Note that the benchmarks do not show any speed-up after this commit. The only significant change happensafter the last commit of the stack, when the `ActiveChainState()` pointer dereference is moved out of the benchmarked code. I ran this in between the commits as well, to exercise all the assertions. Reviewers: #bitcoin_abc, Fabien Reviewed By: #bitcoin_abc, Fabien Differential Revision: https://reviews.bitcoinabc.org/D12170
Summary: UpdateCoins is an unnecessary dependency on validation. All we need to do is add and remove coins to check inputs. We don't need the extra logic for checking coinbases and handling TxUndos. Also remove the wrapper function in validation.h which constructs a throwaway TxUndo object before calling UpdateCoins because it is now unused. This is a backport of [[bitcoin/bitcoin#23157 | core#23157]] [6/8] bitcoin/bitcoin@9e8d7ad Notes: - there was another occurence of the removed `UpdateCoins` function in the unit test added in rABC880d847f076f7b38c849ce3f31120f2f582a0ba0 . For that case, as the tx is a coinbase transaction (without inputs), `UpdateCoins` was just calling `AddCoins`. - the removed `UpdateCoins` function was slighlty modified in D1479 to avoid building an uneccessary `CTxUndo` Depends on D12171 Test Plan: `ninja all check-all` Reviewers: #bitcoin_abc, Fabien Reviewed By: #bitcoin_abc, Fabien Differential Revision: https://reviews.bitcoinabc.org/D12172
Summary: No transaction in the mempool should ever be a coinbase. Since mempoolDuplicate's backend is the chainstate coins view, it should always contain the coins available. This is a backport of [[bitcoin/bitcoin#23157 | core#23157]] [7/8] bitcoin/bitcoin@ed6115f Depends on D12172 Test Plan: `ninja all check-all` Reviewers: #bitcoin_abc, Fabien Reviewed By: #bitcoin_abc, Fabien Differential Revision: https://reviews.bitcoinabc.org/D12173
Summary: Removes check's dependency on validation.h. This is a step towards removing the `txmempool --> validation` circular dependency. From the PR's description: > There are two functions in txmempool that need validation right now: check() and removeForReorg(). This PR removes the dependencies in check(). This concludes backport of [[bitcoin/bitcoin#23157 | core#23157]] [8/8] bitcoin/bitcoin@082c5bf#diff-6875de769e90cec84d2e8a9c1b962cdbcda44d870d42e4215827e599e11e90e3 Depends on D12173 The fuzzer change is not applicable due to missing backports. Test Plan: `ninja all check-all bench-bitcoin` Reviewers: #bitcoin_abc, Fabien Reviewed By: #bitcoin_abc, Fabien Subscribers: Fabien Differential Revision: https://reviews.bitcoinabc.org/D12174
Remove the txmempool <-> validation circular dependency by removing txmempool's dependency on validation. There are two functions in txmempool that need validation right now:
check()andremoveForReorg(). This PR removes the dependencies incheck().This PR also improves the performance of
CTxMemPool::check()by walking through the entries exactly once, in ascending ancestorcount order, which guarantees that we see parents before children.