bench: make `setup()` use single-iteration epochs
What changed, and why it matters
This commit fixes a correctness issue in Bitcoin Core's internal benchmarking tool. Previously, benchmark setup code could run once per batch of repeated measurements, meaning later repeats might not start from a clean state. The change forces setup to run before every single measurement. This only affects benchmark tests, not the live Bitcoin network software, so it has no direct security impact on users.
No security action required. Treat as a normal code-quality/test-harness improvement.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch modifies the nanobench-based benchmark harness so that Bench::setup() automatically sets epochIterations(1) and asserts that no larger epoch size was explicitly requested. Several benchmark files have their redundant epochIterations(1) calls removed. This ensures the setup lambda executes once per timed iteration, preventing state leakage across iterations within an epoch. The change is confined to src/bench/ and does not alter consensus, networking, wallet, or runtime node behavior.
Changed components
src/bench/nanobench.hsrc/bench/addrman.cppsrc/bench/checkblock.cppsrc/bench/coin_selection.cppsrc/bench/load_external.cppsrc/bench/streams_findbyte.cppsrc/bench/verify_script.cppInspect captured patch +11 / −11
diff --git a/src/bench/addrman.cpp b/src/bench/addrman.cpp
index fc081d9f..703b4d24 100644
--- a/src/bench/addrman.cpp
+++ b/src/bench/addrman.cpp
@@ -162,8 +162,7 @@ static void AddrManAddThenGood(benchmark::Bench& bench)
CreateAddresses();
std::optional<AddrMan> addrman;
- bench.epochIterations(1)
- .setup([&] {
+ bench.setup([&] {
addrman.emplace(EMPTY_NETGROUPMAN, /*deterministic=*/false, ADDRMAN_CONSISTENCY_CHECK_RATIO);
AddAddressesToAddrMan(*addrman);
})
diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp
index 9faf9ac1..b943dc0f 100644
--- a/src/bench/checkblock.cpp
+++ b/src/bench/checkblock.cpp
@@ -33,7 +33,7 @@ static void CheckBlockTest(benchmark::Bench& bench)
const auto block_data{benchmark::data::block413567};
CBlock block;
- bench.unit("block").epochIterations(1)
+ bench.unit("block")
.setup([&] {
block = CBlock{};
SpanReader{block_data} >> TX_WITH_WITNESS(block);
diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp
index 7a7f6daf..9f0bf2cc 100644
--- a/src/bench/coin_selection.cpp
+++ b/src/bench/coin_selection.cpp
@@ -115,8 +115,7 @@ static void BnBExhaustion(benchmark::Bench& bench)
{
std::vector<OutputGroup> utxo_pool;
CAmount target;
- bench.epochIterations(1)
- .setup([&] { target = make_hard_case(17, utxo_pool); })
+ bench.setup([&] { target = make_hard_case(17, utxo_pool); })
.run([&] {
auto res{SelectCoinsBnB(utxo_pool, target, /*cost_of_change=*/0, MAX_STANDARD_TX_WEIGHT)}; // Should exhaust
ankerl::nanobench::doNotOptimizeAway(res);
diff --git a/src/bench/load_external.cpp b/src/bench/load_external.cpp
index 128d531f..8e7201ea 100644
--- a/src/bench/load_external.cpp
+++ b/src/bench/load_external.cpp
@@ -62,8 +62,7 @@ static void LoadExternalBlockFile(benchmark::Bench& bench)
std::multimap<uint256, FlatFilePos> blocks_with_unknown_parent;
FlatFilePos pos;
- bench.epochIterations(1)
- .setup([&] {
+ bench.setup([&] {
blocks_with_unknown_parent.clear();
pos = FlatFilePos{};
})
diff --git a/src/bench/nanobench.h b/src/bench/nanobench.h
index 1f798b84..79a384aa 100644
--- a/src/bench/nanobench.h
+++ b/src/bench/nanobench.h
@@ -40,6 +40,7 @@
///////////////////////////////////////////////////////////////////////////////////////////////////
#include <chrono> // high_resolution_clock
+#include <cassert> // assert
#include <cstring> // memcpy
#include <iosfwd> // for std::ostream* custom output target in Config
#include <string> // all names
@@ -1013,7 +1014,7 @@ public:
ANKERL_NANOBENCH(NODISCARD) Config const& config() const noexcept;
/**
- * @brief Configure an untimed setup step per epoch (fluent API).
+ * @brief Configure an untimed setup step per epoch (forces single-iteration epochs).
*
* Example: `bench.setup(...).run(...);`
*/
@@ -1238,6 +1239,9 @@ public:
template <typename Op>
ANKERL_NANOBENCH_NO_SANITIZE("integer")
Bench& run(Op&& op) {
+ assert((mBench.epochIterations() <= 1) &&
+ "setup() runs once per epoch, not once per iteration; it requires epochIterations(1)");
+ mBench.epochIterations(1);
return mBench.runImpl(mSetupOp, std::forward<Op>(op));
}
diff --git a/src/bench/streams_findbyte.cpp b/src/bench/streams_findbyte.cpp
index 47b2ad74..c9ea486c 100644
--- a/src/bench/streams_findbyte.cpp
+++ b/src/bench/streams_findbyte.cpp
@@ -22,8 +22,7 @@ static void FindByte(benchmark::Bench& bench)
file.seek(0, SEEK_SET);
BufferedFile bf{file, /*nBufSize=*/file_size + 1, /*nRewindIn=*/file_size};
- bench.epochIterations(1)
- .setup([&] { bf.SetPos(0); })
+ bench.setup([&] { bf.SetPos(0); })
.run([&] { bf.FindByte(std::byte(1)); });
assert(file.fclose() == 0);
diff --git a/src/bench/verify_script.cpp b/src/bench/verify_script.cpp
index af254ae7..0a92a057 100644
--- a/src/bench/verify_script.cpp
+++ b/src/bench/verify_script.cpp
@@ -115,7 +115,7 @@ static void VerifyNestedIfScript(benchmark::Bench& bench)
for (int i = 0; i < 100; ++i) {
script << OP_ENDIF;
}
- bench.unit("script").epochIterations(1)
+ bench.unit("script")
.setup([&] { stack.clear(); })
.run([&] {
ScriptError error;
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.