2c97a9a doc: describe measuring I/O and memory cost of benchmarks:
The current helpers re-read all 51 blocks from disk while holding cs_main, even though the benchmark just built those blocks.
We could return the blocks from ExtendChainWithSpends() and collect lookup keys from them directly - which would let us chain construction use one uniform loop, validate each block as it is submitted, and remove the duplicate readers.
<details>
<summary>Reuse the constructed blocks</summary>
diff --git a/src/bench/index_blockfilter.cpp b/src/bench/index_blockfilter.cpp
index 9f62301a5a..4ab34520c9 100644
--- a/src/bench/index_blockfilter.cpp
+++ b/src/bench/index_blockfilter.cpp
@@ -72,13 +72,12 @@ static std::unique_ptr<BlockFilterIndex> MakeBlockFilterIndex(TestChain100Setup&
/*n_cache_size=*/1_MiB, f_memory, /*f_wipe=*/true);
}
-// Same sync as BlockFilterIndexSync above, but over blocks carrying
-// transactions paying to distinct scripts, so the filters scale with what a
-// block contains rather than with the number of blocks.
+// Sync blocks containing transactions that pay distinct scripts.
+// Their filters scale with block contents rather than block count.
static void BlockFilterIndexSyncRealistic(benchmark::Bench& bench, bool f_memory)
{
const auto test_setup = MakeNoLogFileContext<TestChain100Setup>();
- ExtendChainWithSpends(*test_setup, BENCH_INDEX_NUM_BLOCKS, BENCH_INDEX_TXS_PER_BLOCK);
+ ExtendChainWithSpends(*test_setup);
BenchIndexSync(bench, *test_setup, [&] { return MakeBlockFilterIndex(*test_setup, f_memory); });
}
diff --git a/src/bench/index_sync_util.cpp b/src/bench/index_sync_util.cpp
index b8c6a242b2..3784647504 100644
--- a/src/bench/index_sync_util.cpp
+++ b/src/bench/index_sync_util.cpp
@@ -5,35 +5,35 @@
#include <bench/index_sync_util.h>
#include <addresstype.h>
-#include <chain.h>
#include <consensus/amount.h>
#include <key.h>
-#include <node/blockstorage.h>
#include <policy/feerate.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <script/script.h>
-#include <sync.h>
+#include <test/util/mining.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <util/check.h>
-#include <validation.h>
#include <chrono>
#include <cstddef>
#include <cstdint>
+#include <memory>
#include <optional>
#include <utility>
#include <vector>
using namespace std::chrono_literals;
-int ExtendChainWithSpends(TestChain100Setup& test_setup, uint32_t num_blocks, uint32_t num_txs_per_block)
+std::vector<CBlock> ExtendChainWithSpends(TestChain100Setup& test_setup)
{
- Assert(num_txs_per_block > 0);
+ // Building the chain is not timed, but it is most of the wall time of a run
+ constexpr uint32_t num_blocks{50};
+ constexpr uint32_t num_txs_per_block{50};
// Block filter elements are deduplicated, so only distinct scripts add to a
- // filter. Keys are reused across blocks, distinct within one.
+ // filter. Key pairs are reused across blocks, distinct within one.
const size_t num_keys{2 * num_txs_per_block};
std::vector<CKey> keys;
std::vector<CTxOut> outs;
@@ -45,99 +45,38 @@ int ExtendChainWithSpends(TestChain100Setup& test_setup, uint32_t num_blocks, ui
}
const CScript coinbase_spk{GetScriptForDestination(WitnessV0KeyHash{test_setup.coinbaseKey.GetPubKey()})};
- const int start_height{WITH_LOCK(::cs_main, return test_setup.m_node.chainman->ActiveHeight())};
// The coinbase of block 1 matures exactly at the first height added here.
- auto& coinbase_to_spend{test_setup.m_coinbase_txns[0]};
- size_t k0{0};
- size_t k1{1};
- const CMutableTransaction first_tx{
- test_setup.CreateValidTransaction(
- /*input_transactions=*/{coinbase_to_spend},
- /*inputs=*/{COutPoint(coinbase_to_spend->GetHash(), 0)},
- /*input_height=*/1,
- /*input_signing_keys=*/{test_setup.coinbaseKey},
- /*outputs=*/{outs[k0], outs[k1]},
- /*feerate=*/{},
- /*fee_output=*/{})
- .first};
- test_setup.CreateAndProcessBlock({first_tx}, coinbase_spk);
- test_setup.m_clock += 1s;
+ CTransactionRef tx_to_spend{test_setup.m_coinbase_txns[0]};
+ std::vector<COutPoint> inputs{{tx_to_spend->GetHash(), 0}};
+ std::vector<CKey> signing_keys{test_setup.coinbaseKey};
+ size_t output_pair{0};
- // first_tx went into start_height + 1, so the loop starts one above that.
- int next_height{start_height + 2};
- CTransactionRef tx_to_spend{MakeTransactionRef(first_tx)};
- size_t next_key{2 % num_keys};
+ std::vector<CBlock> blocks;
+ blocks.reserve(num_blocks);
for (uint32_t b{0}; b < num_blocks; ++b) {
std::vector<CMutableTransaction> txs;
txs.reserve(num_txs_per_block);
for (uint32_t i{0}; i < num_txs_per_block; ++i) {
- const std::vector<COutPoint> inputs{
- COutPoint(tx_to_spend->GetHash(), 0),
- COutPoint(tx_to_spend->GetHash(), 1),
- };
- const std::vector<CKey> signing_keys{keys[k0], keys[k1]};
- // The first transaction of a block spends the last one of the
- // previous block; the rest spend within this one.
- const int input_height{i == 0 ? next_height - 1 : next_height};
- k0 = next_key;
- next_key = (next_key + 1) % num_keys;
- k1 = next_key;
- next_key = (next_key + 1) % num_keys;
- const CMutableTransaction tx{
- test_setup.CreateValidTransaction(
- /*input_transactions=*/{tx_to_spend},
- /*inputs=*/inputs,
- /*input_height=*/input_height,
- /*input_signing_keys=*/signing_keys,
- /*outputs=*/{outs[k0], outs[k1]},
- /*feerate=*/{},
- /*fee_output=*/{})
- .first};
- txs.emplace_back(tx);
+ auto tx{test_setup.CreateValidTransaction(
+ /*input_transactions=*/{tx_to_spend},
+ /*inputs=*/inputs,
+ /*input_height=*/0, // Height is unused outside the scratch signing view
+ /*input_signing_keys=*/signing_keys,
+ /*outputs=*/{outs[2 * output_pair], outs[2 * output_pair + 1]},
+ /*feerate=*/{},
+ /*fee_output=*/{})
+ .first};
tx_to_spend = MakeTransactionRef(tx);
+ txs.push_back(std::move(tx));
+ inputs = {{tx_to_spend->GetHash(), 0}, {tx_to_spend->GetHash(), 1}};
+ signing_keys = {keys[2 * output_pair], keys[2 * output_pair + 1]};
+ output_pair = (output_pair + 1) % num_txs_per_block;
}
- test_setup.CreateAndProcessBlock(txs, coinbase_spk);
- // UpdateTime() clamps a block's time to at least MTP+1, so a frozen
- // clock drifts a second per block and eventually trips
- // MAX_FUTURE_BLOCK_TIME.
+ auto& block{blocks.emplace_back(test_setup.CreateBlock(txs, coinbase_spk))};
+ Assert(!ProcessBlock(test_setup.m_node, std::make_shared<CBlock>(block)).IsNull());
+ // UpdateTime() clamps block times to at least MTP+1, so advance the mock clock like mineBlocks()
test_setup.m_clock += 1s;
- ++next_height;
}
-
- // CreateAndProcessBlock ignores the result of ProcessNewBlock.
- Assert(WITH_LOCK(::cs_main, return test_setup.m_node.chainman->ActiveHeight()) == next_height - 1);
-
- return start_height + 1;
-}
-
-std::vector<Txid> CollectChainTxids(TestChain100Setup& test_setup, int from_height)
-{
- Assert(from_height >= 0);
- std::vector<Txid> txids;
- LOCK(::cs_main);
- const CChain& chain{test_setup.m_node.chainman->ActiveChain()};
- for (int h{from_height}; h <= chain.Height(); ++h) {
- CBlock block;
- Assert(test_setup.m_node.chainman->m_blockman.ReadBlock(block, *chain[h]));
- for (const auto& tx : block.vtx) txids.push_back(tx->GetHash());
- }
- return txids;
-}
-
-std::vector<COutPoint> CollectChainSpentOutpoints(TestChain100Setup& test_setup, int from_height)
-{
- Assert(from_height >= 0);
- std::vector<COutPoint> outpoints;
- LOCK(::cs_main);
- const CChain& chain{test_setup.m_node.chainman->ActiveChain()};
- for (int h{from_height}; h <= chain.Height(); ++h) {
- CBlock block;
- Assert(test_setup.m_node.chainman->m_blockman.ReadBlock(block, *chain[h]));
- for (const auto& tx : block.vtx) {
- if (tx->IsCoinBase()) continue;
- for (const auto& in : tx->vin) outpoints.push_back(in.prevout);
- }
- }
- return outpoints;
+ return blocks;
}
diff --git a/src/bench/index_sync_util.h b/src/bench/index_sync_util.h
index 33a2f249af..300293efa1 100644
--- a/src/bench/index_sync_util.h
+++ b/src/bench/index_sync_util.h
@@ -8,32 +8,24 @@
#include <bench/bench.h>
#include <chain.h>
#include <index/base.h>
-#include <primitives/transaction.h>
+#include <primitives/block.h>
#include <sync.h>
#include <test/util/setup_common.h>
#include <test/util/validation.h>
#include <util/check.h>
#include <validation.h>
-#include <cstdint>
#include <memory>
#include <vector>
-/** Blocks of chained spends the benchmarks build. ExtendChainWithSpends() mines
- * one more on top of these to spend a coinbase, over TestChain100Setup's 100.
- * Building the chain is not timed, but it is most of the wall time of a run. */
-static constexpr uint32_t BENCH_INDEX_NUM_BLOCKS{50};
-/** Chained transactions in each of those blocks. */
-static constexpr uint32_t BENCH_INDEX_TXS_PER_BLOCK{50};
-
/**
- * Extends the active chain with `num_blocks + 1` blocks: one spending a mature
- * coinbase, then `num_blocks` of `num_txs_per_block` chained transactions, each
- * spending the two outputs of the previous one. Returns the first height added.
+ * Extends the active chain with blocks of chained transactions.
+ * The first transaction spends a mature coinbase, and each later transaction spends the two outputs of its predecessor.
+ * Returns the blocks added.
*
* Only callable once per setup: it always spends m_coinbase_txns[0].
*/
-int ExtendChainWithSpends(TestChain100Setup& test_setup, uint32_t num_blocks, uint32_t num_txs_per_block);
+std::vector<CBlock> ExtendChainWithSpends(TestChain100Setup& test_setup);
/**
* Times `make_index()` plus a full Init -> Sync -> Stop cycle over the chain in
@@ -65,12 +57,6 @@ void BenchIndexSync(benchmark::Bench& bench, TestChain100Setup& test_setup, Make
});
}
-/** All txids in the active chain from `from_height` (>= 0) to the tip, in block/tx order. */
-std::vector<Txid> CollectChainTxids(TestChain100Setup& test_setup, int from_height);
-
-/** All outpoints spent by non-coinbase transactions from `from_height` (>= 0) to the tip. */
-std::vector<COutPoint> CollectChainSpentOutpoints(TestChain100Setup& test_setup, int from_height);
-
/**
* Syncs `index`, then times `lookup_one(key)` over every key in `keys`,
* reporting time per lookup via `bench.batch()`. `lookup_one` runs one query
diff --git a/src/bench/index_txindex.cpp b/src/bench/index_txindex.cpp
index c651f3f54f..fe83b2a08d 100644
--- a/src/bench/index_txindex.cpp
+++ b/src/bench/index_txindex.cpp
@@ -6,6 +6,7 @@
#include <bench/index_sync_util.h>
#include <index/txindex.h>
#include <interfaces/chain.h>
+#include <primitives/block.h>
#include <primitives/transaction.h>
#include <test/util/setup_common.h>
#include <util/byte_units.h> // IWYU pragma: keep
@@ -27,7 +28,7 @@ static std::unique_ptr<TxIndex> MakeTxIndex(TestChain100Setup& test_setup, bool
static void TxIndexSync(benchmark::Bench& bench, bool f_memory)
{
const auto test_setup = MakeNoLogFileContext<TestChain100Setup>();
- ExtendChainWithSpends(*test_setup, BENCH_INDEX_NUM_BLOCKS, BENCH_INDEX_TXS_PER_BLOCK);
+ ExtendChainWithSpends(*test_setup);
BenchIndexSync(bench, *test_setup, [&] { return MakeTxIndex(*test_setup, f_memory); });
}
@@ -40,12 +41,14 @@ static void TxIndexSyncMem(benchmark::Bench& bench) { TxIndexSync(bench, /*f_mem
static void TxIndexLookup(benchmark::Bench& bench)
{
const auto test_setup = MakeNoLogFileContext<TestChain100Setup>();
- const int first_spend_height{
- ExtendChainWithSpends(*test_setup, BENCH_INDEX_NUM_BLOCKS, BENCH_INDEX_TXS_PER_BLOCK)};
+ std::vector<Txid> txids;
+ for (const auto& block : ExtendChainWithSpends(*test_setup)) {
+ for (const auto& tx : block.vtx) {
+ txids.push_back(tx->GetHash());
+ }
+ }
auto index{MakeTxIndex(*test_setup, /*f_memory=*/false)};
- const std::vector<Txid> txids{CollectChainTxids(*test_setup, first_spend_height)};
-
BenchIndexLookup(bench, *test_setup, *index, txids, [&](const Txid& id) { return index->FindTx(id).has_value(); });
}
diff --git a/src/bench/index_txospender.cpp b/src/bench/index_txospender.cpp
index dbbc7544a8..3ea8dfb5a7 100644
--- a/src/bench/index_txospender.cpp
+++ b/src/bench/index_txospender.cpp
@@ -6,6 +6,7 @@
#include <bench/index_sync_util.h>
#include <index/txospenderindex.h>
#include <interfaces/chain.h>
+#include <primitives/block.h>
#include <primitives/transaction.h>
#include <test/util/setup_common.h>
#include <util/byte_units.h> // IWYU pragma: keep
@@ -17,8 +18,7 @@
#include <vector>
// Returns a fresh, not-yet-initialized TxoSpenderIndex. See BenchIndexSync()
-// for what f_memory changes. f_wipe empties the database, so the constructor
-// regenerates and writes the siphash key on every iteration.
+// for what f_memory changes.
static std::unique_ptr<TxoSpenderIndex> MakeTxoSpenderIndex(TestChain100Setup& test_setup, bool f_memory)
{
return std::make_unique<TxoSpenderIndex>(interfaces::MakeChain(test_setup.m_node),
@@ -30,7 +30,7 @@ static std::unique_ptr<TxoSpenderIndex> MakeTxoSpenderIndex(TestChain100Setup& t
static void TxoSpenderIndexSync(benchmark::Bench& bench, bool f_memory)
{
const auto test_setup = MakeNoLogFileContext<TestChain100Setup>();
- ExtendChainWithSpends(*test_setup, BENCH_INDEX_NUM_BLOCKS, BENCH_INDEX_TXS_PER_BLOCK);
+ ExtendChainWithSpends(*test_setup);
BenchIndexSync(bench, *test_setup, [&] { return MakeTxoSpenderIndex(*test_setup, f_memory); });
}
@@ -43,12 +43,17 @@ static void TxoSpenderIndexSyncMem(benchmark::Bench& bench) { TxoSpenderIndexSyn
static void TxoSpenderIndexLookup(benchmark::Bench& bench)
{
const auto test_setup = MakeNoLogFileContext<TestChain100Setup>();
- const int first_spend_height{
- ExtendChainWithSpends(*test_setup, BENCH_INDEX_NUM_BLOCKS, BENCH_INDEX_TXS_PER_BLOCK)};
+ std::vector<COutPoint> outpoints;
+ for (const auto& block : ExtendChainWithSpends(*test_setup)) {
+ for (const auto& tx : block.vtx) {
+ if (tx->IsCoinBase()) continue;
+ for (const auto& in : tx->vin) {
+ outpoints.push_back(in.prevout);
+ }
+ }
+ }
auto index{MakeTxoSpenderIndex(*test_setup, /*f_memory=*/false)};
- const std::vector<COutPoint> outpoints{CollectChainSpentOutpoints(*test_setup, first_spend_height)};
-
BenchIndexLookup(bench, *test_setup, *index, outpoints, [&](const COutPoint& txo) {
const auto result{index->FindSpender(txo)};
return result && result->has_value();
</details>