This PR speeds up wallet fast-rescan by executing the filter checks in parallel while ensuring that the filters are updated properly so that no output scripts are missed. Benchmarks, outlined below, show considerable improvement that slows down around 8 threads.
Prerequisite PRs
- #34667 - modify the fast-rescan test to ensure that it fails when the filter is not updated properly.
- #34681 - refactor
CWallet::ScanForWalletTransactionsto prepare for the work in this PR.
Benchmarks
Prepare
- Please tune your system with
pyperf system tune. - Enable block filters by setting
blockfilterindex=1in your bitcoin.conf file and sync your node to the tip. - Parallel fast-rescan will not be used if
chain.isInitialBlockDownload()istrue. Ensure your node is synced before shutting down and running the mainnet benchmark commands.
Using the following command on mainnet with a wallet with no scripts and hyperfine version 1.20.0:
- On master
hyperfine --show-output --export-markdown master.md --export-json master.json \
--sort command \
--runs 3 \
--prepare 'cmake --build build -j 20 && build/bin/bitcoind -blockfilterindex=1 -networkactive=0 && sleep 10 && build/bin/bitcoin-cli loadwalllet <wallet-name>' \
--conclude 'build/bin/bitcoin-cli stop && sleep 10' \
'build/bin/bitcoin-cli rescanblockchain 700000 900000'
- On this PR:
hyperfine --show-output --export-markdown results.md --export-json results.json \
--sort command \
--runs 3 \
-L num_threads 1,2,3,4,5,6,7,8,9,16 \
--prepare 'cmake --build build -j 20 && build/bin/bitcoind -blockfilterindex=1 -networkactive=0 -walletpar={num_threads} && sleep 10 && build/bin/bitcoin-cli loadwalllet <wallet-name>' \
--conclude 'build/bin/bitcoin-cli stop && sleep 10' \
'build/bin/bitcoin-cli rescanblockchain 700000 900000'
<details> <summary>Table 1 was obtained on a machine with the following specifications</summary>
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 46 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 20
On-line CPU(s) list: 0-19
Vendor ID: GenuineIntel
Model name: Intel(R) Core(TM) Ultra 7 265
CPU family: 6
Model: 198
Thread(s) per core: 1
Core(s) per socket: 1
Socket(s): 20
Stepping: 2
CPU(s) scaling MHz: 41%
CPU max MHz: 4800.0000
CPU min MHz: 800.0000
BogoMIPS: 4761.60
</details>
| Branch | Mean [s] | Min [s] | Max [s] |
|---|---|---|---|
| master | 272.222 ± 0.183 | 272.064 | 272.423 |
| new-rescan (num_threads = 1) | 274.423 ± 0.515 | 273.831 | 274.771 |
| new-rescan (num_threads = 2) | 137.240 ± 0.210 | 137.111 | 137.483 |
| new-rescan (num_threads = 4) | 70.159 ± 0.172 | 69.998 | 70.340 |
| new-rescan (num_threads = 6) | 48.351 ± 0.051 | 48.319 | 48.411 |
| new-rescan (num_threads = 8) | 37.955 ± 0.084 | 37.884 | 38.048 |
| new-rescan (num_threads = 10) | 33.271 ± 0.028 | 33.240 | 33.295 |
| new-rescan (num_threads = 16) | 25.295 ± 0.015 | 25.284 | 25.312 |
Table 1. Table of results of a mainnet benchmark of scanning 200,000 blocks on a wallet with no scripts. An empty wallet is not representative of a typical wallet; the next benchmark tries to simulate more "interesting" scenarios.
This Python script patch python script creates custom chains designed with payments at specified intervals to observe the performance of parallel fast rescan.
<details> <summary> Fig 1 shown below was produced using this patch on a machine with the following specifications</summary>
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 48 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 16
On-line CPU(s) list: 0-15
Vendor ID: AuthenticAMD
Model name: AMD Ryzen 9 8945HS w/ Radeon 780M Graphics
CPU family: 25
Model: 117
Thread(s) per core: 2
Core(s) per socket: 8
Socket(s): 1
Stepping: 2
Frequency boost: enabled
CPU(s) scaling MHz: 63%
CPU max MHz: 5263.0000
CPU min MHz: 400.0000
</details>
<img width="2840" height="1639" alt="benchmark_comparison" src="https://github.com/user-attachments/assets/cef4e57b-7727-4a0d-87fd-dbd7d6995747" />
Fig 1. Time to scan a 2000-block chain on Regtest with payments at varying intervals, comparing parallel Fast Rescan against serial Fast Rescan (baseline). These benchmarks use the -walletpar commit as a baseline instead of master, since master lacks the -walletpar config parameter. All materials for this custom benchmark are available here.