Skip to content

Commit 36dd5ed

Browse files
committed
txgraph: Special-case removal of tail of cluster (Optimization)
When transactions are removed from the tail of a cluster, we know the existing linearization remains acceptable (if it already was), but may just need splitting and postlinearization, so special case these into separate quality levels.
1 parent 5801e0f commit 36dd5ed

File tree

1 file changed

+54
-15
lines changed

1 file changed

+54
-15
lines changed

src/txgraph.cpp

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ enum class QualityLevel
3333
{
3434
/** This cluster may have multiple disconnected components, which are all NEEDS_RELINEARIZE. */
3535
NEEDS_SPLIT,
36+
/** This cluster may have multiple disconnected components, which are all ACCEPTABLE. */
37+
NEEDS_SPLIT_ACCEPTABLE,
3638
/** This cluster has undergone changes that warrant re-linearization. */
3739
NEEDS_RELINEARIZE,
3840
/** The minimal level of linearization has been performed, but it is not known to be optimal. */
@@ -79,9 +81,10 @@ class Cluster
7981
// Generic helper functions.
8082

8183
/** Whether the linearization of this Cluster can be exposed. */
82-
bool IsAcceptable() const noexcept
84+
bool IsAcceptable(bool after_split = false) const noexcept
8385
{
84-
return m_quality == QualityLevel::ACCEPTABLE || m_quality == QualityLevel::OPTIMAL;
86+
return m_quality == QualityLevel::ACCEPTABLE || m_quality == QualityLevel::OPTIMAL ||
87+
(after_split && m_quality == QualityLevel::NEEDS_SPLIT_ACCEPTABLE);
8588
}
8689
/** Whether the linearization of this Cluster is optimal. */
8790
bool IsOptimal() const noexcept
@@ -91,7 +94,8 @@ class Cluster
9194
/** Whether this cluster requires splitting. */
9295
bool NeedsSplitting() const noexcept
9396
{
94-
return m_quality == QualityLevel::NEEDS_SPLIT;
97+
return m_quality == QualityLevel::NEEDS_SPLIT ||
98+
m_quality == QualityLevel::NEEDS_SPLIT_ACCEPTABLE;
9599
}
96100
/** Get the number of transactions in this Cluster. */
97101
LinearizationIndex GetTxCount() const noexcept { return m_linearization.size(); }
@@ -379,26 +383,54 @@ void Cluster::ApplyRemovals(TxGraphImpl& graph, std::span<GraphIndex>& to_remove
379383
--graph.m_txcount;
380384
} while(!to_remove.empty());
381385

386+
auto quality = m_quality;
382387
Assume(todo.Any());
383388
// Wipe from the Cluster's DepGraph (this is O(n) regardless of the number of entries
384389
// removed, so we benefit from batching all the removals).
385390
m_depgraph.RemoveTransactions(todo);
386391
m_mapping.resize(m_depgraph.PositionRange());
387392

388-
// Filter removals out of m_linearization.
389-
m_linearization.erase(std::remove_if(
390-
m_linearization.begin(),
391-
m_linearization.end(),
392-
[&](auto pos) { return todo[pos]; }), m_linearization.end());
393-
394-
graph.SetClusterQuality(m_quality, m_setindex, QualityLevel::NEEDS_SPLIT);
393+
// First remove all removals at the end of the linearization.
394+
while (!m_linearization.empty() && todo[m_linearization.back()]) {
395+
todo.Reset(m_linearization.back());
396+
m_linearization.pop_back();
397+
}
398+
if (todo.None()) {
399+
// If no further removals remain, and thus all removals were at the end, we may be able
400+
// to leave the cluster at a better quality level.
401+
if (IsAcceptable(/*after_split=*/true)) {
402+
quality = QualityLevel::NEEDS_SPLIT_ACCEPTABLE;
403+
} else {
404+
quality = QualityLevel::NEEDS_SPLIT;
405+
}
406+
} else {
407+
// If more removals remain, filter those out of m_linearization.
408+
m_linearization.erase(std::remove_if(
409+
m_linearization.begin(),
410+
m_linearization.end(),
411+
[&](auto pos) { return todo[pos]; }), m_linearization.end());
412+
quality = QualityLevel::NEEDS_SPLIT;
413+
}
414+
graph.SetClusterQuality(m_quality, m_setindex, quality);
395415
Updated(graph);
396416
}
397417

398418
bool Cluster::Split(TxGraphImpl& graph) noexcept
399419
{
400420
// This function can only be called when the Cluster needs splitting.
401421
Assume(NeedsSplitting());
422+
// Determine the new quality the split-off Clusters will have.
423+
QualityLevel new_quality = IsAcceptable(/*after_split=*/true) ? QualityLevel::ACCEPTABLE
424+
: QualityLevel::NEEDS_RELINEARIZE;
425+
// If we're going to produce ACCEPTABLE clusters (i.e., when in NEEDS_SPLIT_ACCEPTABLE), we
426+
// need to post-linearize to make sure the split-out versions are all connected (as
427+
// connectivity may have changed by removing part of the cluster). This could be done on each
428+
// resulting split-out cluster separately, but it is simpler to do it once up front before
429+
// splitting. This step is not necessary if the resulting clusters are NEEDS_RELINEARIZE, as
430+
// they will be post-linearized anyway in MakeAcceptable().
431+
if (new_quality == QualityLevel::ACCEPTABLE) {
432+
PostLinearize(m_depgraph, m_linearization);
433+
}
402434
/** Which positions are still left in this Cluster. */
403435
auto todo = m_depgraph.Positions();
404436
/** Mapping from transaction positions in this Cluster to the Cluster where it ends up, and
@@ -412,7 +444,10 @@ bool Cluster::Split(TxGraphImpl& graph) noexcept
412444
if (first && component == todo) {
413445
// The existing Cluster is an entire component. Leave it be, but update its quality.
414446
Assume(todo == m_depgraph.Positions());
415-
graph.SetClusterQuality(m_quality, m_setindex, QualityLevel::NEEDS_RELINEARIZE);
447+
graph.SetClusterQuality(m_quality, m_setindex, new_quality);
448+
// If this made the quality ACCEPTABLE or OPTIMAL, we need to compute and cache its
449+
// chunking.
450+
Updated(graph);
416451
return false;
417452
}
418453
first = false;
@@ -424,7 +459,7 @@ bool Cluster::Split(TxGraphImpl& graph) noexcept
424459
for (auto i : component) {
425460
remap[i] = {new_cluster.get(), DepGraphIndex(-1)};
426461
}
427-
graph.InsertCluster(std::move(new_cluster), QualityLevel::NEEDS_RELINEARIZE);
462+
graph.InsertCluster(std::move(new_cluster), new_quality);
428463
todo -= component;
429464
}
430465
// Redistribute the transactions.
@@ -696,9 +731,11 @@ void TxGraphImpl::SplitAll() noexcept
696731
{
697732
// Before splitting all Cluster, first make sure all removals are applied.
698733
ApplyRemovals();
699-
auto& queue = m_clusters[int(QualityLevel::NEEDS_SPLIT)];
700-
while (!queue.empty()) {
701-
Split(*queue.back().get());
734+
for (auto quality : {QualityLevel::NEEDS_SPLIT, QualityLevel::NEEDS_SPLIT_ACCEPTABLE}) {
735+
auto& queue = m_clusters[int(quality)];
736+
while (!queue.empty()) {
737+
Split(*queue.back().get());
738+
}
702739
}
703740
}
704741

@@ -1221,6 +1258,8 @@ void Cluster::SetFee(TxGraphImpl& graph, DepGraphIndex idx, int64_t fee) noexcep
12211258
m_depgraph.FeeRate(idx).fee = fee;
12221259
if (!NeedsSplitting()) {
12231260
graph.SetClusterQuality(m_quality, m_setindex, QualityLevel::NEEDS_RELINEARIZE);
1261+
} else {
1262+
graph.SetClusterQuality(m_quality, m_setindex, QualityLevel::NEEDS_SPLIT);
12241263
}
12251264
Updated(graph);
12261265
}

0 commit comments

Comments
 (0)