Skip to content
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

tests: Improve runtime of some tests when --enable-debug #26656

Merged
merged 6 commits into from
Dec 19, 2022

Conversation

achow101
Copy link
Member

@achow101 achow101 commented Dec 8, 2022

When configured with --enable-debug, many tests become dramatically slower. These slow downs are particularly noticed in tests that generate a lot of blocks in separate calls, make a lot of RPC calls, or send a lot of data from the test framework's P2P connection. This PR aims to improve the runtime of some of the slower tests and improve the overall runtime of the test runner. This has improved the runtime of the test runner from ~400s to ~140s on my computer.

The slowest test by far was wallet_import_rescan.py. This was taking ~320s. Most of that time was spent waiting for blocks to be mined and then synced to the other nodes. It was generating a new block for every new transaction it was creating in a setup loop. However it is not necessary to have one tx per block. By mining a block only every 10 txs, the runtime is improved to ~61s.

The second slowest test was feature_fee_estimation.py. This test spends most of its time waiting for RPCs to respond. I was able to improve its runtime by batching RPC requests. This has improved the runtime from ~201s to ~140s.

In feature_taproot.py, the test was constructing a Python CScript using a very large list of OP_CHECKSIGs. The constructor for the Python implementation of CScript was iterating this list in order to create a bytes from it even though a bytes could be created from it without iterating. By making the bytes before passing it into the constructor, we are able to improve this test's runtime from ~131s to ~106s.

Although interface_rpc.py was not typically a slow test, I found that it would occasionally have a super long runtime. It typically takes ~7s, but I have observed it taking >400s to run on occasion. This longer runtime occurs more often when --enable-debug. This long runtime was caused by the "exceeding work queue" test which is really just trying to trigger a race condition. In this test, it would create a few threads and try an RPC in a loop in the hopes that eventually one of the RPCs would be added to the work queue while another was processing. It used getrpcinfo for this, but this function is fairly fast. I believe what was happening was that with --enable-debug, all of the code for receiving the RPC would often take longer to run than the RPC itself, so the majority of the requests would succeed, until we got lucky after 10's of thousands of requests. By changing this to use a slow RPC, the race condition can be triggered more reliably, and much sooner as well. I've used waitfornewblock with a 500ms timeout. This improves the runtime to ~3s consistently.

The last test I've changed was rpc_packages.py. This test was one of the higher runtime variability tests. The main source of this variation appears to be waiting for the test node to relay a transaction to the test framework's P2P connection. By whitelisting that peer, the variability is reduced to nearly 0.

Lastly, I've reordered the tests in test_runner.py to account for the slower runtimes when configured with --enable-debug. Some of the slow tests I've looked at were listed as being fast which was causing overall test_runner.py runtime to be extended. This change makes the test runner's runtime be bounded by the slowest test (currently feature_fee_estimation.py with my usual config (-j 60).

@DrahtBot
Copy link
Contributor

DrahtBot commented Dec 8, 2022

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK willcl-ark
Concept ACK fanquake

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #25634 (wallet, tests: Expand and test when the blank wallet flag should be un/set by achow101)
  • #25380 (Detect and ignore transactions that were CPFP'd in the fee estimator by darosior)
  • #24897 ([Draft / POC] Silent Payments by w0xlt)
  • #24748 (test/BIP324: functional tests for v2 P2P encryption by stratospher)
  • #24545 (BIP324: Enable v2 P2P encrypted transport by dhruv)
  • #21422 (Add feerate histogram to getmempoolinfo by kiminuo)
  • #20892 (tests: Run both descriptor and legacy tests within a single test invocation by achow101)

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.

@DrahtBot DrahtBot added the Tests label Dec 8, 2022
@achow101 achow101 force-pushed the faster-test-runner-when-debug branch from e40194e to 5dac98e Compare December 8, 2022 01:55
Copy link
Member

@maflcko maflcko left a comment

Choose a reason for hiding this comment

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

lgtm

test/functional/wallet_import_rescan.py Outdated Show resolved Hide resolved
test/functional/rpc_packages.py Outdated Show resolved Hide resolved
@fanquake
Copy link
Member

fanquake commented Dec 8, 2022

Concept ACK - seeing significant speedup when running the modified tests under --enable-debug. test/functional/test_runner.py wallet_import_rescan.py drops from 418s to 106s.

Generating blocks is slow, especially when --enable-debug. There is no
need to generate a new block for each transaction, so avoid doing that
to improve this test's runtime.
feature_fee_estimation has a lot of loops that hit the RPC many times in
succession in order to setup scenarios. Using batched requests for these
can reduce the test's runtime without effecting the test's behavior.
The sigops draining script in feature_taproot's block_submit was
initialized with a list that would end up always being iterated by
CScript's constructor. Since this list is very large, a lot of time
would be wasted. By creating and passing a bytes object initialized from
that list, we can avoid this iteration and dramatically improve the
runtime of feature_taproot.
The work queue exceeded test in interface_rpc.py would repeatedly call
an RPC until the error was achieved. However hitting this error is
dependent on the processing speed of the computer and the optimization
level of the binary. Configurations that result in slower processing
would result in the RPC used being processed before the error could be
hit, resulting the test's runtime having a high variance.

Switching the RPC to waitfornewblock allows it to run in a much more
consistent time that is still fairly fast. waitfornewblock forces
the RPC server to allocate a thread and wait, occupying a spot in the
work queue. This is perfect for this test because the slower the RPC,
the more likely we will achieve the race condition necessary to pass the
test. Using a timeout of 500 ms appears to work reliably without causing
the test to take too long.
test_submit_child_with_parents creates a p2p connection which waits for
the node to announce transactions to it. By whitelisting this
connection, we can reduce the amount of time spent waiting for this
announcement which improves the test runtime and runtime variance.
The logest running tests should be at the front of the list in
test_runner.py. Since compiling with --enable-debug can have a
significant effect on test runtime, the order is based on the runtime
with that option configured.
@willcl-ark
Copy link
Member

Concept ACK!

This is great to see and is something that has been bothering me for quite some time.

Without this PR tests for me were taking 268 seconds to run, where:

  • wallet_import_rescan 227s,
  • feature_taproot 179s,
  • feature_fee_estimation 267s and
  • feature-block 167s.

After this PR my total test time reduced to 171 seconds, where:

  • wallet_import_rescan 49s!,
  • feature_taproot 148s,
  • feature_fee_estimation 164s and
  • feature_block 170s.

This leaves me with the following tests taking over 1 minute runtime:

❯ cat times | sort -k 3 -n -t "|" -r
feature_block.py                                       | ✓ Passed  | 170 s
feature_fee_estimation.py                              | ✓ Passed  | 164 s
feature_taproot.py                                     | ✓ Passed  | 148 s
mempool_updatefromblock.py                             | ✓ Passed  | 112 s
p2p_segwit.py                                          | ✓ Passed  | 107 s
p2p_blockfilters.py                                    | ✓ Passed  | 100 s
feature_assumevalid.py                                 | ✓ Passed  | 99 s
wallet_taproot.py --descriptors                        | ✓ Passed  | 92 s
p2p_headers_sync_with_minchainwork.py                  | ✓ Passed  | 92 s
wallet_avoidreuse.py --legacy-wallet                   | ✓ Passed  | 87 s
p2p_invalid_messages.py                                | ✓ Passed  | 85 s
wallet_groups.py --descriptors                         | ✓ Passed  | 76 s
wallet_avoidreuse.py --descriptors                     | ✓ Passed  | 76 s
feature_maxuploadtarget.py                             | ✓ Passed  | 71 s
mining_getblocktemplate_longpoll.py                    | ✓ Passed  | 69 s
wallet_groups.py --legacy-wallet                       | ✓ Passed  | 63 s
wallet_fundrawtransaction.py --descriptors             | ✓ Passed  | 60 s

Copy link
Member

@willcl-ark willcl-ark left a comment

Choose a reason for hiding this comment

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

ACK 1647a11

The final tests to finish for me with --enable-debug and 50 jobs are generally:

245/255 - feature_coinstatsindex.py passed, Duration: 25 s
246/255 - p2p_headers_sync_with_minchainwork.py passed, Duration: 93 s
247/255 - wallet_taproot.py --descriptors passed, Duration: 94 s
248/255 - feature_config_args.py passed, Duration: 35 s
249/255 - p2p_blockfilters.py passed, Duration: 105 s
250/255 - feature_assumevalid.py passed, Duration: 105 s
251/255 - p2p_segwit.py passed, Duration: 107 s
252/255 - mempool_updatefromblock.py passed, Duration: 116 s
253/255 - feature_taproot.py passed, Duration: 155 s
254/255 - feature_block.py passed, Duration: 175 s
255/255 - feature_fee_estimation.py passed, Duration: 209 s

So the new ordering of the list feels pretty good.

@maflcko maflcko merged commit bd13d6b into bitcoin:master Dec 19, 2022
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Dec 19, 2022
@bitcoin bitcoin locked and limited conversation to collaborators Dec 19, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants