clusterlin: avoid heap allocations in GetLinearization
What changed, and why it matters
This is a routine performance optimization in Bitcoin Core's transaction-cluster linearization code. It replaces dynamically-allocated vectors with fixed-size stack arrays to avoid repeated heap memory allocations. There is no security-relevant change: the algorithm, ordering logic, and dependency handling remain identical.
No security action required. Treat as normal performance refactor.
Security signals we found
No strong security signals were identified.
Evidence from the diff
In src/cluster_linearize.h, GetLinearization previously used four std::vector objects (ready_chunks, chunk_deps, tx_deps, ready_tx) that were heap-allocated on each call. The patch switches these to std::array of fixed size SetType::Size() and tracks active length with explicit counters. All heap operations (make_heap, push_heap, pop_heap) now operate on begin() to begin()+count. The change is purely an allocation/performance optimization; no logic, bounds, or ordering semantics are altered.
Changed components
src/cluster_linearize.hGetLinearizationInspect captured patch +26 / −20
diff --git a/src/cluster_linearize.h b/src/cluster_linearize.h
index 23cb98f4..eb9da60a 100644
--- a/src/cluster_linearize.h
+++ b/src/cluster_linearize.h
@@ -1471,16 +1471,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 +1555,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 +1590,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);
}
}
}
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.