bench: make setup() use single-iteration epochs

`setup()` in nanobench runs once per epoch, not once per timed call.
If an epoch executes the benchmark body multiple times, `setup()` can silently leave later iterations with different preconditions.

Make `setup()` force `epochIterations(1)` itself: keep rejecting incompatible larger explicit epoch sizes, but allow existing single-iteration callers such as `-sanity-check`.

With `setup()` handling this centrally, remove the redundant `epochIterations(1)` calls from the benchmarks that use it.

Co-authored-by: David Gumberg <davidzgumberg@gmail.com>
This commit is contained in:
Lőrinc
2026-04-23 20:27:56 +02:00
parent ba0078e3bf
commit e6430b2773
7 changed files with 11 additions and 11 deletions

View File

@@ -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);
})

View File

@@ -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);

View File

@@ -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);

View File

@@ -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{};
})

View File

@@ -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));
}

View File

@@ -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);

View File

@@ -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;