txgraph: drop NEEDS_SPLIT_ACCEPTABLE (simplification)

With the SFL algorithm, we will practically be capable of keeping
most if not all clusters optimal. With that, it seems less valuable
to avoid doing work after splitting an acceptable cluster, because by
doing some work we may get it to OPTIMAL.

This reduces the complexity of the code a bit as well.
This commit is contained in:
Pieter Wuille committed 2026-01-05 11:48:16 -05:00
1 parent 01ffcf464a
commit 62dd88624a
1 file changed
+14 -49
+14 -49
View File
@@ -42,8 +42,6 @@ enum class QualityLevel
OVERSIZED_SINGLETON, OVERSIZED_SINGLETON,
/** This cluster may have multiple disconnected components, which are all NEEDS_RELINEARIZE. */ /** This cluster may have multiple disconnected components, which are all NEEDS_RELINEARIZE. */
NEEDS_SPLIT, NEEDS_SPLIT,
/** This cluster may have multiple disconnected components, which are all ACCEPTABLE. */
NEEDS_SPLIT_ACCEPTABLE,
/** This cluster has undergone changes that warrant re-linearization. */ /** This cluster has undergone changes that warrant re-linearization. */
NEEDS_RELINEARIZE, NEEDS_RELINEARIZE,
/** The minimal level of linearization has been performed, but it is not known to be optimal. */ /** The minimal level of linearization has been performed, but it is not known to be optimal. */
@@ -128,10 +126,9 @@ public:
// Generic helper functions. // Generic helper functions.
/** Whether the linearization of this Cluster can be exposed. */ /** Whether the linearization of this Cluster can be exposed. */
bool IsAcceptable(bool after_split = false) const noexcept bool IsAcceptable() const noexcept
{ {
return m_quality == QualityLevel::ACCEPTABLE || m_quality == QualityLevel::OPTIMAL || return m_quality == QualityLevel::ACCEPTABLE || m_quality == QualityLevel::OPTIMAL;
(after_split && m_quality == QualityLevel::NEEDS_SPLIT_ACCEPTABLE);
} }
/** Whether the linearization of this Cluster is optimal. */ /** Whether the linearization of this Cluster is optimal. */
bool IsOptimal() const noexcept bool IsOptimal() const noexcept
@@ -145,8 +142,7 @@ public:
/** Whether this cluster requires splitting. */ /** Whether this cluster requires splitting. */
bool NeedsSplitting() const noexcept bool NeedsSplitting() const noexcept
{ {
return m_quality == QualityLevel::NEEDS_SPLIT || return m_quality == QualityLevel::NEEDS_SPLIT;
m_quality == QualityLevel::NEEDS_SPLIT_ACCEPTABLE;
} }
/** Get the smallest number of transactions this Cluster is intended for. */ /** Get the smallest number of transactions this Cluster is intended for. */
@@ -1179,37 +1175,20 @@ void GenericClusterImpl::ApplyRemovals(TxGraphImpl& graph, int level, std::span<
to_remove = to_remove.subspan(1); to_remove = to_remove.subspan(1);
} while(!to_remove.empty()); } while(!to_remove.empty());
auto quality = m_quality;
Assume(todo.Any()); Assume(todo.Any());
// Wipe from the Cluster's DepGraph (this is O(n) regardless of the number of entries // Wipe from the Cluster's DepGraph (this is O(n) regardless of the number of entries
// removed, so we benefit from batching all the removals). // removed, so we benefit from batching all the removals).
m_depgraph.RemoveTransactions(todo); m_depgraph.RemoveTransactions(todo);
m_mapping.resize(m_depgraph.PositionRange()); m_mapping.resize(m_depgraph.PositionRange());
// First remove all removals at the end of the linearization. // Filter removed transactions out of m_linearization.
while (!m_linearization.empty() && todo[m_linearization.back()]) { m_linearization.erase(std::remove_if(m_linearization.begin(), m_linearization.end(),
todo.Reset(m_linearization.back()); [&](auto pos) { return todo[pos]; }),
m_linearization.pop_back(); m_linearization.end());
}
if (todo.None()) {
// If no further removals remain, and thus all removals were at the end, we may be able
// to leave the cluster at a better quality level.
if (IsAcceptable(/*after_split=*/true)) {
quality = QualityLevel::NEEDS_SPLIT_ACCEPTABLE;
} else {
quality = QualityLevel::NEEDS_SPLIT;
}
} else {
// If more removals remain, filter those out of m_linearization.
m_linearization.erase(std::remove_if(
m_linearization.begin(),
m_linearization.end(),
[&](auto pos) { return todo[pos]; }), m_linearization.end());
quality = QualityLevel::NEEDS_SPLIT;
}
Compact(); Compact();
graph.GetClusterSet(level).m_cluster_usage += TotalMemoryUsage(); graph.GetClusterSet(level).m_cluster_usage += TotalMemoryUsage();
graph.SetClusterQuality(level, m_quality, m_setindex, quality); graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::NEEDS_SPLIT);
Updated(graph, level); Updated(graph, level);
} }
@@ -1354,17 +1333,7 @@ bool GenericClusterImpl::Split(TxGraphImpl& graph, int level) noexcept
// This function can only be called when the Cluster needs splitting. // This function can only be called when the Cluster needs splitting.
Assume(NeedsSplitting()); Assume(NeedsSplitting());
// Determine the new quality the split-off Clusters will have. // Determine the new quality the split-off Clusters will have.
QualityLevel new_quality = IsAcceptable(/*after_split=*/true) ? QualityLevel::ACCEPTABLE QualityLevel new_quality = QualityLevel::NEEDS_RELINEARIZE;
: QualityLevel::NEEDS_RELINEARIZE;
// If we're going to produce ACCEPTABLE clusters (i.e., when in NEEDS_SPLIT_ACCEPTABLE), we
// need to post-linearize to make sure the split-out versions are all connected (as
// connectivity may have changed by removing part of the cluster). This could be done on each
// resulting split-out cluster separately, but it is simpler to do it once up front before
// splitting. This step is not necessary if the resulting clusters are NEEDS_RELINEARIZE, as
// they will be post-linearized anyway in MakeAcceptable().
if (new_quality == QualityLevel::ACCEPTABLE) {
PostLinearize(m_depgraph, m_linearization);
}
/** Which positions are still left in this Cluster. */ /** Which positions are still left in this Cluster. */
auto todo = m_depgraph.Positions(); auto todo = m_depgraph.Positions();
/** Mapping from transaction positions in this Cluster to the Cluster where it ends up, and /** Mapping from transaction positions in this Cluster to the Cluster where it ends up, and
@@ -1771,11 +1740,9 @@ void TxGraphImpl::SplitAll(int up_to_level) noexcept
// Before splitting all Cluster, first make sure all removals are applied. // Before splitting all Cluster, first make sure all removals are applied.
ApplyRemovals(up_to_level); ApplyRemovals(up_to_level);
for (int level = 0; level <= up_to_level; ++level) { for (int level = 0; level <= up_to_level; ++level) {
for (auto quality : {QualityLevel::NEEDS_SPLIT, QualityLevel::NEEDS_SPLIT_ACCEPTABLE}) { auto& queue = GetClusterSet(level).m_clusters[int(QualityLevel::NEEDS_SPLIT)];
auto& queue = GetClusterSet(level).m_clusters[int(quality)]; while (!queue.empty()) {
while (!queue.empty()) { Split(*queue.back().get(), level);
Split(*queue.back().get(), level);
}
} }
} }
} }
@@ -2640,10 +2607,8 @@ void GenericClusterImpl::SetFee(TxGraphImpl& graph, int level, DepGraphIndex idx
m_depgraph.FeeRate(idx).fee = fee; m_depgraph.FeeRate(idx).fee = fee;
if (m_quality == QualityLevel::OVERSIZED_SINGLETON) { if (m_quality == QualityLevel::OVERSIZED_SINGLETON) {
// Nothing to do. // Nothing to do.
} else if (!NeedsSplitting()) { } else if (IsAcceptable()) {
graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::NEEDS_RELINEARIZE); graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::NEEDS_RELINEARIZE);
} else {
graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::NEEDS_SPLIT);
} }
Updated(graph, level); Updated(graph, level);
} }