Skip to content

Commit 2965fbf

Browse files
committed
clusterlin: track upper bound potential set for work items (optimization)
In each work item, keep track of a conservative overestimate of the best possible feerate that can be reached from it, and then use these to avoid exploring hopeless work items.
1 parent 9e43e4c commit 2965fbf

File tree

2 files changed

+61
-10
lines changed

2 files changed

+61
-10
lines changed

src/cluster_linearize.h

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,14 @@ struct SetInfo
279279
explicit SetInfo(const DepGraph<SetType>& depgraph, const SetType& txn) noexcept :
280280
transactions(txn), feerate(depgraph.FeeRate(txn)) {}
281281

282+
/** Add a transaction to this SetInfo (which must not yet be in it). */
283+
void Set(const DepGraph<SetType>& depgraph, ClusterIndex pos) noexcept
284+
{
285+
Assume(!transactions[pos]);
286+
transactions.Set(pos);
287+
feerate += depgraph.FeeRate(pos);
288+
}
289+
282290
/** Add the transactions of other to this SetInfo (no overlap allowed). */
283291
SetInfo& operator|=(const SetInfo& other) noexcept
284292
{
@@ -658,16 +666,24 @@ class SearchCandidateFinder
658666
/** Set of undecided transactions. This must be a subset of m_todo, and have no overlap
659667
* with inc. The set (inc | und) must be topologically valid. */
660668
SetType und;
669+
/** (Only when inc is not empty) The best feerate of any superset of inc that is also a
670+
* subset of (inc | und), without requiring it to be topologically valid. It forms a
671+
* conservative upper bound on how good a set this work item can give rise to. */
672+
FeeFrac pot_feerate;
661673

662674
/** Construct a new work item. */
663-
WorkItem(SetInfo<SetType>&& i, SetType&& u) noexcept :
664-
inc(std::move(i)), und(std::move(u)) {}
675+
WorkItem(SetInfo<SetType>&& i, SetType&& u, FeeFrac&& p_f) noexcept :
676+
inc(std::move(i)), und(std::move(u)), pot_feerate(std::move(p_f))
677+
{
678+
Assume(pot_feerate.IsEmpty() == inc.feerate.IsEmpty());
679+
}
665680

666681
/** Swap two WorkItems. */
667682
void Swap(WorkItem& other) noexcept
668683
{
669684
swap(inc, other.inc);
670685
swap(und, other.und);
686+
swap(pot_feerate, other.pot_feerate);
671687
}
672688
};
673689

@@ -687,7 +703,9 @@ class SearchCandidateFinder
687703
// processing loop below, and during the add_fn/split_fn calls, we do not need to deal
688704
// with the best=empty case.
689705
if (best.feerate.IsEmpty()) best = SetInfo(m_sorted_depgraph, component);
690-
queue.emplace_back(/*inc=*/SetInfo<SetType>{}, /*und=*/std::move(component));
706+
queue.emplace_back(/*inc=*/SetInfo<SetType>{},
707+
/*und=*/std::move(component),
708+
/*pot_feerate=*/FeeFrac{});
691709
} while (to_cover.Any());
692710

693711
/** Local copy of the iteration limit. */
@@ -700,23 +718,44 @@ class SearchCandidateFinder
700718
* - und: the "und" value for the new work item ((inc | und) must be topological).
701719
*/
702720
auto add_fn = [&](SetInfo<SetType> inc, SetType und) noexcept {
721+
/** SetInfo object with the set whose feerate will become the new work item's
722+
* pot_feerate. It starts off equal to inc. */
723+
auto pot = inc;
703724
if (!inc.feerate.IsEmpty()) {
725+
// Add entries to pot.
726+
for (auto pos : und) {
727+
// Determine if adding transaction pos to pot (ignoring topology) would improve
728+
// it. If not, we're done updating pot. This relies on the fact that
729+
// m_sorted_depgraph, and thus the transactions iterated over, are in decreasing
730+
// individual feerate order.
731+
if (!(m_sorted_depgraph.FeeRate(pos) >> pot.feerate)) break;
732+
pot.Set(m_sorted_depgraph, pos);
733+
}
734+
704735
// If inc's feerate is better than best's, remember it as our new best.
705736
if (inc.feerate > best.feerate) {
706737
best = inc;
707738
}
739+
740+
// If no potential transactions exist beyond the already included ones, no
741+
// improvement is possible anymore.
742+
if (pot.feerate.size == inc.feerate.size) return;
743+
// At this point und must be non-empty. If it were empty then pot would equal inc.
744+
Assume(und.Any());
708745
} else {
709746
Assume(inc.transactions.None());
747+
// If inc is empty, we just make sure there are undecided transactions left to
748+
// split on.
749+
if (und.None()) return;
710750
}
711751

712-
// Make sure there are undecided transactions left to split on.
713-
if (und.None()) return;
714-
715752
// Actually construct a new work item on the queue. Due to the switch to DFS when queue
716753
// space runs out (see below), we know that no reallocation of the queue should ever
717754
// occur.
718755
Assume(queue.size() < queue.capacity());
719-
queue.emplace_back(/*inc=*/std::move(inc), /*und=*/std::move(und));
756+
queue.emplace_back(/*inc=*/std::move(inc),
757+
/*und=*/std::move(und),
758+
/*pot_feerate=*/std::move(pot.feerate));
720759
};
721760

722761
/** Internal process function. It takes an existing work item, and splits it in two: one
@@ -730,9 +769,21 @@ class SearchCandidateFinder
730769
Assume(elem.inc.transactions.IsSubsetOf(m_todo) && elem.und.IsSubsetOf(m_todo));
731770
// Included transactions cannot be undecided.
732771
Assume(!elem.inc.transactions.Overlaps(elem.und));
772+
// If pot is empty, then so is inc.
773+
Assume(elem.inc.feerate.IsEmpty() == elem.pot_feerate.IsEmpty());
774+
775+
const ClusterIndex first = elem.und.First();
776+
if (!elem.inc.feerate.IsEmpty()) {
777+
// We can ignore any queue item whose potential feerate isn't better than the best
778+
// seen so far.
779+
if (elem.pot_feerate <= best.feerate) return;
780+
} else {
781+
// In case inc is empty use a simpler alternative check.
782+
if (m_sorted_depgraph.FeeRate(first) <= best.feerate) return;
783+
}
733784

734785
// Pick the first undecided transaction as the one to split on.
735-
const ClusterIndex split = elem.und.First();
786+
const ClusterIndex split = first;
736787

737788
// Add a work item corresponding to exclusion of the split transaction.
738789
const auto& desc = m_sorted_depgraph.Descendants(split);

src/test/fuzz/cluster_linearize.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ FUZZ_TARGET(clusterlin_chunking)
429429
SetInfo<TestBitSet> accumulator, best;
430430
for (ClusterIndex idx : linearization) {
431431
if (todo[idx]) {
432-
accumulator |= SetInfo(depgraph, idx);
432+
accumulator.Set(depgraph, idx);
433433
if (best.feerate.IsEmpty() || accumulator.feerate >> best.feerate) {
434434
best = accumulator;
435435
}
@@ -658,7 +658,7 @@ FUZZ_TARGET(clusterlin_linearization_chunking)
658658
SetInfo<TestBitSet> accumulator, best;
659659
for (auto j : linearization) {
660660
if (todo[j] && !combined[j]) {
661-
accumulator |= SetInfo(depgraph, j);
661+
accumulator.Set(depgraph, j);
662662
if (best.feerate.IsEmpty() || accumulator.feerate > best.feerate) {
663663
best = accumulator;
664664
}

0 commit comments

Comments
 (0)