Skip to content

Commit 9ad2fe7

Browse files
committed
clusterlin: only start/use search when enough iterations left
1 parent bd04435 commit 9ad2fe7

File tree

2 files changed

+47
-12
lines changed

2 files changed

+47
-12
lines changed

src/cluster_linearize.h

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,12 @@ class AncestorCandidateFinder
536536
return m_todo.None();
537537
}
538538

539+
/** Count the number of remaining unlinearized transactions. */
540+
ClusterIndex NumRemaining() const noexcept
541+
{
542+
return m_todo.Count();
543+
}
544+
539545
/** Find the best (highest-feerate, smallest among those in case of a tie) ancestor set
540546
* among the remaining transactions. Requires !AllDone().
541547
*
@@ -960,10 +966,20 @@ std::pair<std::vector<ClusterIndex>, bool> Linearize(const DepGraph<SetType>& de
960966
std::vector<ClusterIndex> linearization;
961967

962968
AncestorCandidateFinder anc_finder(depgraph);
963-
SearchCandidateFinder src_finder(depgraph, rng_seed);
969+
std::optional<SearchCandidateFinder<SetType>> src_finder;
964970
linearization.reserve(depgraph.TxCount());
965971
bool optimal = true;
966972

973+
// Treat the initialization of SearchCandidateFinder as taking N^2/64 (rounded up) iterations
974+
// (largely due to the cost of constructing the internal sorted-by-feerate DepGraph inside
975+
// SearchCandidateFinder), a rough approximation based on benchmark. If we don't have that
976+
// many, don't start it.
977+
uint64_t start_iterations = (uint64_t{depgraph.TxCount()} * depgraph.TxCount() + 63) / 64;
978+
if (iterations_left > start_iterations) {
979+
iterations_left -= start_iterations;
980+
src_finder.emplace(depgraph, rng_seed);
981+
}
982+
967983
/** Chunking of what remains of the old linearization. */
968984
LinearizationChunking old_chunking(depgraph, old_linearization);
969985

@@ -976,12 +992,22 @@ std::pair<std::vector<ClusterIndex>, bool> Linearize(const DepGraph<SetType>& de
976992
auto best = anc_finder.FindCandidateSet();
977993
if (!best_prefix.feerate.IsEmpty() && best_prefix.feerate >= best.feerate) best = best_prefix;
978994

979-
// Invoke bounded search to update best, with up to half of our remaining iterations as
980-
// limit.
981-
uint64_t max_iterations_now = (iterations_left + 1) / 2;
982995
uint64_t iterations_done_now = 0;
983-
std::tie(best, iterations_done_now) = src_finder.FindCandidateSet(max_iterations_now, best);
984-
iterations_left -= iterations_done_now;
996+
uint64_t max_iterations_now = 0;
997+
if (src_finder) {
998+
// Treat the invocation of SearchCandidateFinder::FindCandidateSet() as costing N/4
999+
// up-front (rounded up) iterations (largely due to the cost of connected-component
1000+
// splitting), a rough approximation based on benchmarks.
1001+
uint64_t base_iterations = (anc_finder.NumRemaining() + 3) / 4;
1002+
if (iterations_left > base_iterations) {
1003+
// Invoke bounded search to update best, with up to half of our remaining
1004+
// iterations as limit.
1005+
iterations_left -= base_iterations;
1006+
max_iterations_now = (iterations_left + 1) / 2;
1007+
std::tie(best, iterations_done_now) = src_finder->FindCandidateSet(max_iterations_now, best);
1008+
iterations_left -= iterations_done_now;
1009+
}
1010+
}
9851011

9861012
if (iterations_done_now == max_iterations_now) {
9871013
optimal = false;
@@ -999,7 +1025,7 @@ std::pair<std::vector<ClusterIndex>, bool> Linearize(const DepGraph<SetType>& de
9991025
// Update state to reflect best is no longer to be linearized.
10001026
anc_finder.MarkDone(best.transactions);
10011027
if (anc_finder.AllDone()) break;
1002-
src_finder.MarkDone(best.transactions);
1028+
if (src_finder) src_finder->MarkDone(best.transactions);
10031029
if (old_chunking.NumChunksLeft() > 0) {
10041030
old_chunking.MarkDone(best.transactions);
10051031
}

src/test/fuzz/cluster_linearize.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,7 @@ FUZZ_TARGET(clusterlin_ancestor_finder)
458458
while (todo.Any()) {
459459
// Call the ancestor finder's FindCandidateSet for what remains of the graph.
460460
assert(!anc_finder.AllDone());
461+
assert(todo.Count() == anc_finder.NumRemaining());
461462
auto best_anc = anc_finder.FindCandidateSet();
462463
// Sanity check the result.
463464
assert(best_anc.transactions.Any());
@@ -489,6 +490,7 @@ FUZZ_TARGET(clusterlin_ancestor_finder)
489490
anc_finder.MarkDone(del_set);
490491
}
491492
assert(anc_finder.AllDone());
493+
assert(anc_finder.NumRemaining() == 0);
492494
}
493495

494496
static constexpr auto MAX_SIMPLE_ITERATIONS = 300000;
@@ -523,6 +525,7 @@ FUZZ_TARGET(clusterlin_search_finder)
523525
assert(!smp_finder.AllDone());
524526
assert(!exh_finder.AllDone());
525527
assert(!anc_finder.AllDone());
528+
assert(anc_finder.NumRemaining() == todo.Count());
526529

527530
// For each iteration, read an iteration count limit from the fuzz input.
528531
uint64_t max_iterations = 1;
@@ -605,6 +608,7 @@ FUZZ_TARGET(clusterlin_search_finder)
605608
assert(smp_finder.AllDone());
606609
assert(exh_finder.AllDone());
607610
assert(anc_finder.AllDone());
611+
assert(anc_finder.NumRemaining() == 0);
608612
}
609613

610614
FUZZ_TARGET(clusterlin_linearization_chunking)
@@ -775,11 +779,16 @@ FUZZ_TARGET(clusterlin_linearize)
775779
if (n <= 19 && iter_count > (uint64_t{1} << n)) {
776780
assert(optimal);
777781
}
778-
// Additionally, if the assumption of sqrt(2^k)+1 iterations per step holds, the maximum number
779-
// of iterations is also bounded by (2 + sqrt(2)) * (sqrt(2^n) - 1) + n, which is less than
780-
// (2 + sqrt(2)) * sqrt(2^n) + n. Subtracting n and squaring gives
781-
// (6 + 4 * sqrt(2)) * 2^n < 12 * 2^n.
782-
if (n <= 35 && iter_count > n && (iter_count - n) * (iter_count - n) >= uint64_t{12} << n) {
782+
// Additionally, if the assumption of sqrt(2^k)+1 iterations per step holds, plus ceil(k/4)
783+
// start-up cost per step, plus ceil(n^2/64) start-up cost overall, we can compute the upper
784+
// bound for a whole linearization (summing for k=1..n) using the Python expression
785+
// [sum((k+3)//4 + int(math.sqrt(2**k)) + 1 for k in range(1, n + 1)) + (n**2 + 63) // 64 for n in range(0, 35)]:
786+
static constexpr uint64_t MAX_OPTIMAL_ITERS[] = {
787+
0, 4, 8, 12, 18, 26, 37, 51, 70, 97, 133, 182, 251, 346, 480, 666, 927, 1296, 1815, 2545,
788+
3576, 5031, 7087, 9991, 14094, 19895, 28096, 39690, 56083, 79263, 112041, 158391, 223936,
789+
316629, 447712
790+
};
791+
if (n < std::size(MAX_OPTIMAL_ITERS) && iter_count >= MAX_OPTIMAL_ITERS[n]) {
783792
Assume(optimal);
784793
}
785794

0 commit comments

Comments
 (0)