clusterlin: unidirectional MakeTopological initially (optimization)
What changed, and why it matters
This commit is an internal performance optimization for Bitcoin Core's transaction clustering algorithm. It changes how the code tries to combine groups of related transactions during fee estimation, doing less work upfront while still checking both directions for newly merged groups. The test file updates expected iteration counts accordingly. There is no indication this change affects security, consensus, network behavior, or user funds.
No security action required. Review as ordinary code-quality/performance change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch modifies MakeTopological() in src/cluster_linearize.h to initially attempt merges in only one randomly chosen direction (init_dir) rather than both, and only performs bidirectional merges for chunks produced by earlier merges. A merged_chunks set tracks which chunks need full bidirectional treatment. The accompanying test utility MaxOptimalLinearizationIters updates its precomputed maximum iteration table to reflect the new (generally lower) bounds. The change is purely algorithmic optimization within the cluster linearization module.
Changed components
src/cluster_linearize.hsrc/test/util/cluster_linearize.hInspect captured patch +23 / −7
diff --git a/src/cluster_linearize.h b/src/cluster_linearize.h
index 164ee0df..7e20f802 100644
--- a/src/cluster_linearize.h
+++ b/src/cluster_linearize.h
@@ -1129,6 +1129,16 @@ public:
void MakeTopological() noexcept
{
Assume(m_suboptimal_chunks.empty());
+ /** What direction to initially merge chunks in; one of the two directions is enough. This
+ * is sufficient because if a non-topological inactive dependency exists between two
+ * chunks, at least one of the two chunks will eventually be processed in a direction that
+ * discovers it - either the lower chunk tries upward, or the upper chunk tries downward.
+ * Chunks that are the result of the merging are always tried in both directions. */
+ unsigned init_dir = m_rng.randbool();
+ /** Which chunks are the result of merging, and thus need merge attempts in both
+ * directions. */
+ SetType merged_chunks;
+ // Mark chunks as suboptimal.
m_suboptimal_idxs = m_chunk_idxs;
for (auto chunk_idx : m_chunk_idxs) {
m_suboptimal_chunks.emplace_back(chunk_idx);
@@ -1147,9 +1157,12 @@ public:
// If what was popped is not currently a chunk, continue. This may
// happen when it was merged with something else since being added.
if (!m_chunk_idxs[chunk_idx]) continue;
+ /** What direction(s) to attempt merging in. 1=up, 2=down, 3=both. */
+ unsigned direction = merged_chunks[chunk_idx] ? 3 : init_dir + 1;
int flip = m_rng.randbool();
for (int i = 0; i < 2; ++i) {
if (i ^ flip) {
+ if (!(direction & 1)) continue;
// Attempt to merge the chunk upwards.
auto result_up = MergeStep<false>(chunk_idx);
if (result_up != INVALID_SET_IDX) {
@@ -1157,9 +1170,11 @@ public:
m_suboptimal_idxs.Set(result_up);
m_suboptimal_chunks.push_back(result_up);
}
+ merged_chunks.Set(result_up);
break;
}
} else {
+ if (!(direction & 2)) continue;
// Attempt to merge the chunk downwards.
auto result_down = MergeStep<true>(chunk_idx);
if (result_down != INVALID_SET_IDX) {
@@ -1167,6 +1182,7 @@ public:
m_suboptimal_idxs.Set(result_down);
m_suboptimal_chunks.push_back(result_down);
}
+ merged_chunks.Set(result_down);
break;
}
}
diff --git a/src/test/util/cluster_linearize.h b/src/test/util/cluster_linearize.h
index d5686068..3814c3fc 100644
--- a/src/test/util/cluster_linearize.h
+++ b/src/test/util/cluster_linearize.h
@@ -403,13 +403,13 @@ inline uint64_t MaxOptimalLinearizationIters(DepGraphIndex cluster_count)
static constexpr uint64_t ITERS[65] = {
0,
0, 4, 10, 34, 76, 156, 229, 380,
- 432, 517, 678, 896, 1037, 1366, 1464, 1792,
- 2060, 2542, 3068, 3116, 4029, 3467, 5324, 5512,
- 6481, 7161, 7441, 8183, 8843, 9353, 11104, 11269,
- 11791, 11871, 13367, 14259, 14229, 12397, 13581, 18152,
- 18737, 16581, 23217, 24044, 29597, 29030, 34069, 34594,
- 33630, 26227, 34471, 38815, 40814, 31182, 40305, 34019,
- 36582, 55659, 39994, 41277, 42365, 52822, 59733, 67035
+ 441, 517, 678, 933, 1037, 1366, 1464, 1711,
+ 2111, 2542, 3068, 3116, 4029, 3467, 5324, 5402,
+ 6481, 7161, 7441, 8183, 8843, 9353, 11104, 11455,
+ 11791, 12570, 13480, 14259, 14525, 12426, 14477, 20201,
+ 18737, 16581, 23622, 28486, 30652, 33021, 32942, 32745,
+ 34046, 26227, 34662, 38019, 40814, 31113, 41448, 33968,
+ 35024, 59207, 42872, 41277, 42365, 51833, 63410, 67035
};
assert(cluster_count < std::size(ITERS));
// Multiply the table number by two, to account for the fact that they are not absolutes.
Why this scored 13/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.