mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-07-29 09:38:52 +02:00
Merge bitcoin/bitcoin#35694: clusterlin: minor SFL optimizations
efb4eae338clusterlin: avoid recomputing intersections in MergeChunks (Pieter Wuille)4b91ad149fclusterlin: reserve the suboptimal-chunk queue up front (Pieter Wuille)e6ca996255clusterlin: avoid heap allocations in GetLinearization (Pieter Wuille) Pull request description: These are a few minor and easy-to-review code changes to the SFL algorithm, which net a few % speedup (~2.6% speedup on `LinearizeOptimally*` benchmarks, ~5% on the `Historical` ones). <details><summary>LLM benchmark results:</summary> | Class | Ratio | 95% CI | |-------------------|--------|------------------| | HistoricalTotal | 0.9499 | [0.9312, 0.9709] | | SyntheticTotal | 0.9760 | [0.9597, 0.9949] | | HistoricalPerCost | 0.9545 | [0.9238, 0.9845] | | SyntheticPerCost | 0.9934 | [0.9688, 1.0175] | | **All (60)** | **0.9737** | **[0.9563, 0.9913]** | Methology: One release-mode `bench_bitcoin` binary (GCC 15.2, `-O2`) was built for the base commit and one for the branch tip, and run as `bench_bitcoin -filter='.*LinearizeOptimally.*' -min-time=100` in 85 strictly alternating pairs of fresh process launches (170 launches, ~20 minutes total), each pinned to the same core of an otherwise idle Zen5 machine. For each of the 60 benchmarks, the per-launch ns/op values were averaged over each binary's 85 launches; the optimized/base ratios of these means were aggregated as a geometric mean per benchmark class, with 95% confidence intervals from 1000 bootstrap resamplings of the launches. </details> Disclosure: this code, comments, benchmarks, and selection of optimizations were done by Claude Fable 5. I reviewed the commits, and wrote the PR description. ACKs for top commit: optout21: ACKefb4eae338instagibbs: light review ACKefb4eae338marcofleon: crACKefb4eae338Tree-SHA512: 6bd523e8bc56dbc8ff2540ef26355e79895a8a11a26d08e22918aea0c002ee8f96916654d5a2b792515ef12ee022c65d71ab9a778f937a264aaf6e4ed6167b71
This commit is contained in:
@@ -954,11 +954,16 @@ private:
|
||||
Assume(m_chunk_idxs[bottom_idx]);
|
||||
auto& top_chunk_info = m_set_info[top_idx];
|
||||
auto& bottom_chunk_info = m_set_info[bottom_idx];
|
||||
// Count the number of dependencies between bottom_chunk and top_chunk.
|
||||
// Count the number of dependencies between bottom_chunk and top_chunk, remembering the
|
||||
// per-transaction counts so the picking loop below does not need to recompute the
|
||||
// intersections.
|
||||
unsigned num_deps{0};
|
||||
std::array<SetIdx, SetType::Size()> counts;
|
||||
for (auto tx_idx : top_chunk_info.transactions) {
|
||||
auto& tx_data = m_tx_data[tx_idx];
|
||||
num_deps += (tx_data.children & bottom_chunk_info.transactions).Count();
|
||||
auto count = (tx_data.children & bottom_chunk_info.transactions).Count();
|
||||
counts[tx_idx] = count;
|
||||
num_deps += count;
|
||||
}
|
||||
m_cost.MergeChunksMid(/*num_txns=*/top_chunk_info.transactions.Count());
|
||||
Assume(num_deps > 0);
|
||||
@@ -967,10 +972,10 @@ private:
|
||||
unsigned num_steps = 0;
|
||||
for (auto tx_idx : top_chunk_info.transactions) {
|
||||
++num_steps;
|
||||
auto& tx_data = m_tx_data[tx_idx];
|
||||
auto intersect = tx_data.children & bottom_chunk_info.transactions;
|
||||
auto count = intersect.Count();
|
||||
auto count = counts[tx_idx];
|
||||
if (pick < count) {
|
||||
auto& tx_data = m_tx_data[tx_idx];
|
||||
auto intersect = tx_data.children & bottom_chunk_info.transactions;
|
||||
for (auto child_idx : intersect) {
|
||||
if (pick == 0) {
|
||||
m_cost.MergeChunksEnd(/*num_steps=*/num_steps);
|
||||
@@ -1183,6 +1188,7 @@ public:
|
||||
m_tx_data.resize(depgraph.PositionRange());
|
||||
m_set_info.resize(num_transactions);
|
||||
m_reachable.resize(num_transactions);
|
||||
m_suboptimal_chunks.reserve(num_transactions);
|
||||
size_t num_chunks = 0;
|
||||
size_t num_deps = 0;
|
||||
for (auto tx_idx : m_transaction_idxs) {
|
||||
@@ -1471,16 +1477,22 @@ public:
|
||||
/** A heap with all chunks (by set index) that can currently be included, sorted by
|
||||
* chunk feerate (high to low), chunk size (small to large), and by least maximum element
|
||||
* according to the fallback order (which is the second pair element). */
|
||||
std::vector<std::pair<SetIdx, TxIdx>> ready_chunks;
|
||||
std::array<std::pair<SetIdx, TxIdx>, SetType::Size()> ready_chunks;
|
||||
/** The number of entries of ready_chunks in use. */
|
||||
unsigned num_ready_chunks{0};
|
||||
/** For every chunk, indexed by SetIdx, the number of unmet dependencies the chunk has on
|
||||
* other chunks (not including dependencies within the chunk itself). */
|
||||
std::vector<TxIdx> chunk_deps(m_set_info.size(), 0);
|
||||
std::array<TxIdx, SetType::Size()> chunk_deps;
|
||||
std::fill_n(chunk_deps.begin(), m_set_info.size(), TxIdx{0});
|
||||
/** For every transaction, indexed by TxIdx, the number of unmet dependencies the
|
||||
* transaction has. */
|
||||
std::vector<TxIdx> tx_deps(m_tx_data.size(), 0);
|
||||
std::array<TxIdx, SetType::Size()> tx_deps;
|
||||
std::fill_n(tx_deps.begin(), m_tx_data.size(), TxIdx{0});
|
||||
/** A heap with all transactions within the current chunk that can be included, sorted by
|
||||
* tx feerate (high to low), tx size (small to large), and fallback order. */
|
||||
std::vector<TxIdx> ready_tx;
|
||||
std::array<TxIdx, SetType::Size()> ready_tx;
|
||||
/** The number of entries of ready_tx in use. */
|
||||
unsigned num_ready_tx{0};
|
||||
// Populate chunk_deps and tx_deps.
|
||||
unsigned num_deps{0};
|
||||
for (TxIdx chl_idx : m_transaction_idxs) {
|
||||
@@ -1549,31 +1561,31 @@ public:
|
||||
// Construct a heap with all chunks that have no out-of-chunk dependencies.
|
||||
for (SetIdx chunk_idx : m_chunk_idxs) {
|
||||
if (chunk_deps[chunk_idx] == 0) {
|
||||
ready_chunks.emplace_back(chunk_idx, max_fallback_fn(chunk_idx));
|
||||
ready_chunks[num_ready_chunks++] = {chunk_idx, max_fallback_fn(chunk_idx)};
|
||||
}
|
||||
}
|
||||
std::make_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn);
|
||||
std::make_heap(ready_chunks.begin(), ready_chunks.begin() + num_ready_chunks, chunk_cmp_fn);
|
||||
// Pop chunks off the heap.
|
||||
while (!ready_chunks.empty()) {
|
||||
while (num_ready_chunks > 0) {
|
||||
auto [chunk_idx, _rnd] = ready_chunks.front();
|
||||
std::pop_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn);
|
||||
ready_chunks.pop_back();
|
||||
std::pop_heap(ready_chunks.begin(), ready_chunks.begin() + num_ready_chunks, chunk_cmp_fn);
|
||||
--num_ready_chunks;
|
||||
Assume(chunk_deps[chunk_idx] == 0);
|
||||
const auto& chunk_txn = m_set_info[chunk_idx].transactions;
|
||||
// Build heap of all includable transactions in chunk.
|
||||
Assume(ready_tx.empty());
|
||||
Assume(num_ready_tx == 0);
|
||||
for (TxIdx tx_idx : chunk_txn) {
|
||||
if (tx_deps[tx_idx] == 0) ready_tx.push_back(tx_idx);
|
||||
if (tx_deps[tx_idx] == 0) ready_tx[num_ready_tx++] = tx_idx;
|
||||
}
|
||||
Assume(!ready_tx.empty());
|
||||
std::make_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn);
|
||||
Assume(num_ready_tx > 0);
|
||||
std::make_heap(ready_tx.begin(), ready_tx.begin() + num_ready_tx, tx_cmp_fn);
|
||||
// Pick transactions from the ready heap, append them to linearization, and decrement
|
||||
// dependency counts.
|
||||
while (!ready_tx.empty()) {
|
||||
while (num_ready_tx > 0) {
|
||||
// Pop an element from the tx_ready heap.
|
||||
auto tx_idx = ready_tx.front();
|
||||
std::pop_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn);
|
||||
ready_tx.pop_back();
|
||||
std::pop_heap(ready_tx.begin(), ready_tx.begin() + num_ready_tx, tx_cmp_fn);
|
||||
--num_ready_tx;
|
||||
// Append to linearization.
|
||||
ret.push_back(tx_idx);
|
||||
// Decrement dependency counts.
|
||||
@@ -1584,16 +1596,16 @@ public:
|
||||
Assume(tx_deps[chl_idx] > 0);
|
||||
if (--tx_deps[chl_idx] == 0 && chunk_txn[chl_idx]) {
|
||||
// Child tx has no dependencies left, and is in this chunk. Add it to the tx heap.
|
||||
ready_tx.push_back(chl_idx);
|
||||
std::push_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn);
|
||||
ready_tx[num_ready_tx++] = chl_idx;
|
||||
std::push_heap(ready_tx.begin(), ready_tx.begin() + num_ready_tx, tx_cmp_fn);
|
||||
}
|
||||
// Decrement chunk dependency count if this is out-of-chunk dependency.
|
||||
if (chl_data.chunk_idx != chunk_idx) {
|
||||
Assume(chunk_deps[chl_data.chunk_idx] > 0);
|
||||
if (--chunk_deps[chl_data.chunk_idx] == 0) {
|
||||
// Child chunk has no dependencies left. Add it to the chunk heap.
|
||||
ready_chunks.emplace_back(chl_data.chunk_idx, max_fallback_fn(chl_data.chunk_idx));
|
||||
std::push_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn);
|
||||
ready_chunks[num_ready_chunks++] = {chl_data.chunk_idx, max_fallback_fn(chl_data.chunk_idx)};
|
||||
std::push_heap(ready_chunks.begin(), ready_chunks.begin() + num_ready_chunks, chunk_cmp_fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user