clusterlin: minimize chunks (feature)

After the normal optimization process finishes, and finds an optimal
spanning forest, run a second process (while computation budget remains)
to split chunks into minimal equal-feerate chunks.
This commit is contained in:
Pieter Wuille committed 2026-01-12 17:38:30 -05:00
1 parent abc6a3a4eb
commit da56ef239b
5 files changed
+235 -29

No files matched your search

+199 -8
View File
@@ -479,7 +479,7 @@ std::vector<FeeFrac> ChunkLinearization(const DepGraph<SetType>& depgraph, std::
* feerate, each individually sorted in an arbitrary but topological (= no child before parent) * feerate, each individually sorted in an arbitrary but topological (= no child before parent)
* way. * way.
* *
* We define three quality properties the state can have, each being stronger than the previous: * We define four quality properties the state can have:
* *
* - acyclic: The state is acyclic whenever no cycle of active dependencies exists within the * - acyclic: The state is acyclic whenever no cycle of active dependencies exists within the
* graph, ignoring the parent/child direction. This is equivalent to saying that within * graph, ignoring the parent/child direction. This is equivalent to saying that within
@@ -529,7 +529,19 @@ std::vector<FeeFrac> ChunkLinearization(const DepGraph<SetType>& depgraph, std::
* satisfied. Thus, the state being optimal is more a "the eventual output is *known* * satisfied. Thus, the state being optimal is more a "the eventual output is *known*
* to be optimal". * to be optimal".
* *
* The algorithm terminates whenever an optimal state is reached. * - minimal: We say the state is minimal when it is:
* - acyclic
* - topological, except that inactive dependencies between equal-feerate chunks are
* allowed as long as they do not form a loop.
* - like optimal, no active dependencies whose top feerate is strictly higher than
* the bottom feerate are allowed.
* - no chunk contains a proper non-empty subset which includes all its own in-chunk
* dependencies of the same feerate as the chunk itself.
*
* A minimal state effectively corresponds to an optimal state, where every chunk has
* been split into its minimal equal-feerate components.
*
* The algorithm terminates whenever a minimal state is reached.
* *
* *
* This leads to the following high-level algorithm: * This leads to the following high-level algorithm:
@@ -539,6 +551,10 @@ std::vector<FeeFrac> ChunkLinearization(const DepGraph<SetType>& depgraph, std::
* - Loop until optimal (no dependencies with higher-feerate top than bottom), or time runs out: * - Loop until optimal (no dependencies with higher-feerate top than bottom), or time runs out:
* - Deactivate a violating dependency, potentially making the state non-topological. * - Deactivate a violating dependency, potentially making the state non-topological.
* - Activate other dependencies to make the state topological again. * - Activate other dependencies to make the state topological again.
* - If there is time left and the state is optimal:
* - Attempt to split chunks into equal-feerate parts without mutual dependencies between them.
* When this succeeds, recurse into them.
* - If no such chunks can be found, the state is minimal.
* - Output the chunks from high to low feerate, each internally sorted topologically. * - Output the chunks from high to low feerate, each internally sorted topologically.
* *
* When merging, we always either: * When merging, we always either:
@@ -558,6 +574,7 @@ std::vector<FeeFrac> ChunkLinearization(const DepGraph<SetType>& depgraph, std::
* - Deactivate D, causing the chunk it is in to split into top T and bottom B. * - Deactivate D, causing the chunk it is in to split into top T and bottom B.
* - Do an upwards merge of T, if possible. If so, repeat the same with the merged result. * - Do an upwards merge of T, if possible. If so, repeat the same with the merged result.
* - Do a downwards merge of B, if possible. If so, repeat the same with the merged result. * - Do a downwards merge of B, if possible. If so, repeat the same with the merged result.
* - Split chunks further to obtain a minimal state, see below.
* - Output the chunks from high to low feerate, each internally sorted topologically. * - Output the chunks from high to low feerate, each internally sorted topologically.
* *
* Instead of performing merges arbitrarily to make the initial state topological, it is possible * Instead of performing merges arbitrarily to make the initial state topological, it is possible
@@ -569,6 +586,21 @@ std::vector<FeeFrac> ChunkLinearization(const DepGraph<SetType>& depgraph, std::
* - Do an upwards merge of C, if possible. If so, repeat the same with the merged result. * - Do an upwards merge of C, if possible. If so, repeat the same with the merged result.
* No downwards merges are needed in this case. * No downwards merges are needed in this case.
* *
* After reaching an optimal state, it can be transformed into a minimal state by attempting to
* split chunks further into equal-feerate parts. To do so, pick a specific transaction in each
* chunk (the pivot), and rerun the above split-then-merge procedure again:
* - first, while pretending the pivot transaction has an infinitesimally higher (or lower) fee
* than it really has. If a split exists with the pivot in the top part (or bottom part), this
* will find it.
* - if that fails to split, repeat while pretending the pivot transaction has an infinitesimally
* lower (or higher) fee. If a split exists with the pivot in the bottom part (or top part), this
* will find it.
* - if either succeeds, repeat the procedure for the newly found chunks to split them further.
* If not, the chunk is already minimal.
* If the chunk can be split into equal-feerate parts, then the pivot must exist in either the top
* or bottom part of that potential split. By trying both with the same pivot, if a split exists,
* it will be found.
*
* What remains to be specified are a number of heuristics: * What remains to be specified are a number of heuristics:
* *
* - How to decide which chunks to merge: * - How to decide which chunks to merge:
@@ -644,10 +676,30 @@ private:
std::vector<DepData> m_dep_data; std::vector<DepData> m_dep_data;
/** A FIFO of chunk representatives of chunks that may be improved still. */ /** A FIFO of chunk representatives of chunks that may be improved still. */
VecDeque<TxIdx> m_suboptimal_chunks; VecDeque<TxIdx> m_suboptimal_chunks;
/** A FIFO of chunk representatives with a pivot transaction in them, and a flag to indicate
* their status:
* - bit 1: currently attempting to move the pivot down, rather than up.
* - bit 2: this is the second stage, so we have already tried moving the pivot in the other
* direction.
*/
VecDeque<std::tuple<TxIdx, TxIdx, unsigned>> m_nonminimal_chunks;
/** The number of updated transactions in activations/deactivations. */ /** The number of updated transactions in activations/deactivations. */
uint64_t m_cost{0}; uint64_t m_cost{0};
/** Pick a random transaction within a set (which must be non-empty). */
TxIdx PickRandomTx(const SetType& tx_idxs) noexcept
{
Assume(tx_idxs.Any());
unsigned pos = m_rng.randrange<unsigned>(tx_idxs.Count());
for (auto tx_idx : tx_idxs) {
if (pos == 0) return tx_idx;
--pos;
}
Assume(false);
return TxIdx(-1);
}
/** Update a chunk: /** Update a chunk:
* - All transactions have their chunk representative set to `chunk_rep`. * - All transactions have their chunk representative set to `chunk_rep`.
* - All dependencies which have `query` in their top_setinfo get `dep_change` added to it * - All dependencies which have `query` in their top_setinfo get `dep_change` added to it
@@ -769,8 +821,9 @@ private:
/*chunk_rep=*/bottom_rep, /*dep_change=*/top_part); /*chunk_rep=*/bottom_rep, /*dep_change=*/top_part);
} }
/** Activate a dependency from the chunk represented by bottom_rep to the chunk represented by /** Activate a dependency from the chunk represented by bottom_idx to the chunk represented by
* top_rep, which must exist. Return the representative of the merged chunk. */ * top_idx. Return the representative of the merged chunk, or TxIdx(-1) if no merge is
* possible. */
TxIdx MergeChunks(TxIdx top_rep, TxIdx bottom_rep) noexcept TxIdx MergeChunks(TxIdx top_rep, TxIdx bottom_rep) noexcept
{ {
auto& top_chunk = m_tx_data[top_rep]; auto& top_chunk = m_tx_data[top_rep];
@@ -783,7 +836,7 @@ private:
auto& tx_data = m_tx_data[tx]; auto& tx_data = m_tx_data[tx];
num_deps += (tx_data.children & bottom_chunk.chunk_setinfo.transactions).Count(); num_deps += (tx_data.children & bottom_chunk.chunk_setinfo.transactions).Count();
} }
Assume(num_deps > 0); if (num_deps == 0) return TxIdx(-1);
// Uniformly randomly pick one of them and activate it. // Uniformly randomly pick one of them and activate it.
TxIdx pick = m_rng.randrange(num_deps); TxIdx pick = m_rng.randrange(num_deps);
for (auto tx : top_chunk.chunk_setinfo.transactions) { for (auto tx : top_chunk.chunk_setinfo.transactions) {
@@ -1068,6 +1121,119 @@ public:
return false; return false;
} }
/** Initialize data structure for minimizing the chunks. Can only be called if state is known
* to be optimal. OptimizeStep() cannot be called anymore afterwards. */
void StartMinimizing() noexcept
{
m_nonminimal_chunks.clear();
m_nonminimal_chunks.reserve(m_transaction_idxs.Count());
// Gather all chunks, and for each, add it with a random pivot in it, and a random initial
// direction, to m_nonminimal_chunks.
for (auto tx : m_transaction_idxs) {
auto& tx_data = m_tx_data[tx];
if (tx_data.chunk_rep == tx) {
TxIdx pivot_idx = PickRandomTx(tx_data.chunk_setinfo.transactions);
m_nonminimal_chunks.emplace_back(tx, pivot_idx, m_rng.randbits<1>());
// Randomize the initial order of nonminimal chunks in the queue.
TxIdx j = m_rng.randrange<TxIdx>(m_nonminimal_chunks.size());
if (j != m_nonminimal_chunks.size() - 1) {
std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[j]);
}
}
}
}
/** Try to reduce a chunk's size. Returns false if all chunks are minimal, true otherwise. */
bool MinimizeStep() noexcept
{
// If the queue of potentially-non-minimal chunks is empty, we are done.
if (m_nonminimal_chunks.empty()) return false;
// Pop an entry from the potentially-non-minimal chunk queue.
auto [chunk_rep, pivot_idx, flags] = m_nonminimal_chunks.front();
m_nonminimal_chunks.pop_front();
auto& chunk_data = m_tx_data[chunk_rep];
Assume(chunk_data.chunk_rep == chunk_rep);
/** Whether to move the pivot down rather than up. */
bool move_pivot_down = flags & 1;
/** Whether this is already the second stage. */
bool second_stage = flags & 2;
// Find a random dependency whose top and bottom set feerates are equal, and which has
// pivot in bottom set (if move_pivot_down) or in top set (if !move_pivot_down).
DepIdx candidate_dep = DepIdx(-1);
uint64_t candidate_tiebreak{0};
bool have_any = false;
// Iterate over all transactions.
for (auto tx_idx : chunk_data.chunk_setinfo.transactions) {
const auto& tx_data = m_tx_data[tx_idx];
// Iterate over all active child dependencies of the transaction.
for (auto dep_idx : tx_data.child_deps) {
auto& dep_data = m_dep_data[dep_idx];
// Skip inactive child dependencies.
if (!dep_data.active) continue;
// Skip if this dependency does not have equal top and bottom set feerates. Note
// that the top cannot have higher feerate than the bottom, or OptimizeSteps would
// have dealt with it.
if (dep_data.top_setinfo.feerate << chunk_data.chunk_setinfo.feerate) continue;
have_any = true;
// Skip if this dependency does not have pivot in the right place.
if (move_pivot_down == dep_data.top_setinfo.transactions[pivot_idx]) continue;
// Remember this as our chosen dependency if it has a better tiebreak.
uint64_t tiebreak = m_rng.rand64() | 1;
if (tiebreak > candidate_tiebreak) {
candidate_tiebreak = tiebreak;
candidate_dep = dep_idx;
}
}
}
// If no dependencies have equal top and bottom set feerate, this chunk is minimal.
if (!have_any) return true;
// If all found dependencies have the pivot in the wrong place, try moving it in the other
// direction. If this was the second stage already, we are done.
if (candidate_tiebreak == 0) {
// Switch to other direction, and to second phase.
flags ^= 3;
if (!second_stage) m_nonminimal_chunks.emplace_back(chunk_rep, pivot_idx, flags);
return true;
}
// Otherwise, deactivate the dependency that was found.
Deactivate(candidate_dep);
auto& dep_data = m_dep_data[candidate_dep];
auto parent_chunk_rep = m_tx_data[dep_data.parent].chunk_rep;
auto child_chunk_rep = m_tx_data[dep_data.child].chunk_rep;
// Try to activate a dependency between the new bottom and the new top (opposite from the
// dependency that was just deactivated).
auto merged_chunk_rep = MergeChunks(child_chunk_rep, parent_chunk_rep);
if (merged_chunk_rep != TxIdx(-1)) {
// A self-merge happened.
// Re-insert the chunk into the queue, in the same direction. Note that the chunk_rep
// will have changed.
m_nonminimal_chunks.emplace_back(merged_chunk_rep, pivot_idx, flags);
} else {
// No self-merge happens, and thus we have found a way to split the chunk. Create two
// smaller chunks, and add them to the queue. The one that contains the current pivot
// gets to continue with it in the same direction, to minimize the number of times we
// alternate direction. If we were in the second phase already, the newly created chunk
// inherits that too, because we know no split with the pivot on the other side is
// possible already. The new chunk without the current pivot gets a new randomly-chosen
// one.
if (move_pivot_down) {
auto parent_pivot_idx = PickRandomTx(m_tx_data[parent_chunk_rep].chunk_setinfo.transactions);
m_nonminimal_chunks.emplace_back(parent_chunk_rep, parent_pivot_idx, m_rng.randbits<1>());
m_nonminimal_chunks.emplace_back(child_chunk_rep, pivot_idx, flags);
} else {
auto child_pivot_idx = PickRandomTx(m_tx_data[child_chunk_rep].chunk_setinfo.transactions);
m_nonminimal_chunks.emplace_back(parent_chunk_rep, pivot_idx, flags);
m_nonminimal_chunks.emplace_back(child_chunk_rep, child_pivot_idx, m_rng.randbits<1>());
}
if (m_rng.randbool()) {
std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[m_nonminimal_chunks.size() - 2]);
}
}
return true;
}
/** Construct a topologically-valid linearization from the current forest state. Must be /** Construct a topologically-valid linearization from the current forest state. Must be
* topological. */ * topological. */
std::vector<DepGraphIndex> GetLinearization() noexcept std::vector<DepGraphIndex> GetLinearization() noexcept
@@ -1182,6 +1348,10 @@ public:
* After an OptimizeStep(), the diagram will always be at least as good as before. Once * After an OptimizeStep(), the diagram will always be at least as good as before. Once
* OptimizeStep() returns false, the diagram will be equivalent to that produced by * OptimizeStep() returns false, the diagram will be equivalent to that produced by
* GetLinearization(), and optimal. * GetLinearization(), and optimal.
*
* After a MinimizeStep(), the diagram cannot change anymore (in the CompareChunks() sense),
* but its number of segments can increase still. Once MinimizeStep() returns false, the number
* of chunks of the produced linearization will match the number of segments in the diagram.
*/ */
std::vector<FeeFrac> GetDiagram() const noexcept std::vector<FeeFrac> GetDiagram() const noexcept
{ {
@@ -1328,6 +1498,19 @@ public:
auto tx_idx = m_suboptimal_chunks[i]; auto tx_idx = m_suboptimal_chunks[i];
assert(m_transaction_idxs[tx_idx]); assert(m_transaction_idxs[tx_idx]);
} }
//
// Verify m_nonminimal_chunks.
//
SetType nonminimal_reps;
for (size_t i = 0; i < m_nonminimal_chunks.size(); ++i) {
auto [chunk_rep, pivot, flags] = m_nonminimal_chunks[i];
assert(m_tx_data[chunk_rep].chunk_rep == chunk_rep);
assert(m_tx_data[pivot].chunk_rep == chunk_rep);
assert(!nonminimal_reps[chunk_rep]);
nonminimal_reps.Set(chunk_rep);
}
assert(nonminimal_reps.IsSubsetOf(m_transaction_idxs));
} }
}; };
@@ -1345,7 +1528,7 @@ public:
* - The resulting linearization. It is guaranteed to be at least as * - The resulting linearization. It is guaranteed to be at least as
* good (in the feerate diagram sense) as old_linearization. * good (in the feerate diagram sense) as old_linearization.
* - A boolean indicating whether the result is guaranteed to be * - A boolean indicating whether the result is guaranteed to be
* optimal. * optimal with minimal chunks.
* - How many optimization steps were actually performed. * - How many optimization steps were actually performed.
*/ */
template<typename SetType> template<typename SetType>
@@ -1361,11 +1544,19 @@ std::tuple<std::vector<DepGraphIndex>, bool, uint64_t> Linearize(const DepGraph<
} }
// Make improvement steps to it until we hit the max_iterations limit, or an optimal result // Make improvement steps to it until we hit the max_iterations limit, or an optimal result
// is found. // is found.
bool optimal = false;
if (forest.GetCost() < max_iterations) { if (forest.GetCost() < max_iterations) {
forest.StartOptimizing(); forest.StartOptimizing();
do { do {
if (!forest.OptimizeStep()) { if (!forest.OptimizeStep()) break;
} while (forest.GetCost() < max_iterations);
}
// Make chunk minimization steps until we hit the max_iterations limit, or all chunks are
// minimal.
bool optimal = false;
if (forest.GetCost() < max_iterations) {
forest.StartMinimizing();
do {
if (!forest.MinimizeStep()) {
optimal = true; optimal = true;
break; break;
} }
+2 -4
View File
@@ -94,10 +94,8 @@ void TestOptimalLinearization(const std::vector<uint8_t>& enc, const std::vector
SanityCheck(depgraph, lin); SanityCheck(depgraph, lin);
auto chunking = ChunkLinearization(depgraph, lin); auto chunking = ChunkLinearization(depgraph, lin);
BOOST_CHECK(std::is_eq(CompareChunks(chunking, optimal_diagram))); BOOST_CHECK(std::is_eq(CompareChunks(chunking, optimal_diagram)));
// TODO: temporarily disabled; SFL does not guarantee minimal chunks. This will be // Verify that the chunks are minimal.
// reinstated in a future commit. BOOST_CHECK_EQUAL(chunking.size(), optimal_diagram.size());
// // Verify that the chunks are minimal.
// BOOST_CHECK(chunking.size() == optimal_diagram.size());
} }
tx_count = depgraph.PositionRange(); tx_count = depgraph.PositionRange();
}; };
+25 -7
View File
@@ -914,7 +914,8 @@ FUZZ_TARGET(clusterlin_sfl)
// Function to test the state. // Function to test the state.
std::vector<FeeFrac> last_diagram; std::vector<FeeFrac> last_diagram;
auto test_fn = [&](bool is_optimal = false) { bool was_optimal{false};
auto test_fn = [&](bool is_optimal = false, bool is_minimal = false) {
if (rng.randbits(4) == 0) { if (rng.randbits(4) == 0) {
// Perform sanity checks from time to time (too computationally expensive to do after // Perform sanity checks from time to time (too computationally expensive to do after
// every step). // every step).
@@ -930,13 +931,21 @@ FUZZ_TARGET(clusterlin_sfl)
assert(cmp_lin >= 0); assert(cmp_lin >= 0);
// If we're in an allegedly optimal state, they must match. // If we're in an allegedly optimal state, they must match.
if (is_optimal) assert(cmp_lin == 0); if (is_optimal) assert(cmp_lin == 0);
// If we're in an allegedly minimal state, they must also have the same number of
// segments.
if (is_minimal) assert(diagram.size() == lin_diagram.size());
} }
// Verify that subsequent calls to GetDiagram() never get worse/incomparable. // Verify that subsequent calls to GetDiagram() never get worse/incomparable.
if (!last_diagram.empty()) { if (!last_diagram.empty()) {
auto cmp = CompareChunks(diagram, last_diagram); auto cmp = CompareChunks(diagram, last_diagram);
assert(cmp >= 0); assert(cmp >= 0);
// If the last diagram was already optimal, the new one cannot be better.
if (was_optimal) assert(cmp == 0);
// Also, if the diagram was already optimal, the number of segments can only increase.
if (was_optimal) assert(diagram.size() >= last_diagram.size());
} }
last_diagram = std::move(diagram); last_diagram = std::move(diagram);
was_optimal = is_optimal;
}; };
if (load_linearization) { if (load_linearization) {
@@ -963,7 +972,15 @@ FUZZ_TARGET(clusterlin_sfl)
test_fn(); test_fn();
if (!sfl.OptimizeStep()) break; if (!sfl.OptimizeStep()) break;
} }
// Loop until minimal.
test_fn(/*is_optimal=*/true); test_fn(/*is_optimal=*/true);
sfl.StartMinimizing();
while (true) {
test_fn(/*is_optimal=*/true);
if (!sfl.MinimizeStep()) break;
}
test_fn(/*is_optimal=*/true, /*is_minimal=*/true);
// Verify that optimality is reached within an expected amount of work. This protects against // Verify that optimality is reached within an expected amount of work. This protects against
// hypothetical bugs that hugely increase the amount of work needed to reach optimality. // hypothetical bugs that hugely increase the amount of work needed to reach optimality.
@@ -975,6 +992,9 @@ FUZZ_TARGET(clusterlin_sfl)
auto simple_cmp = CompareChunks(last_diagram, simple_diagram); auto simple_cmp = CompareChunks(last_diagram, simple_diagram);
assert(simple_cmp >= 0); assert(simple_cmp >= 0);
if (simple_optimal) assert(simple_cmp == 0); if (simple_optimal) assert(simple_cmp == 0);
// If the diagram matches, we must also have at least as many segments (because the SFL state
// and its produced diagram are minimal);
if (simple_cmp == 0) assert(last_diagram.size() >= simple_diagram.size());
// We can compare with any arbitrary linearization, and the diagram must be at least as good as // We can compare with any arbitrary linearization, and the diagram must be at least as good as
// each. // each.
@@ -983,6 +1003,7 @@ FUZZ_TARGET(clusterlin_sfl)
auto read_diagram = ChunkLinearization(depgraph, read_lin); auto read_diagram = ChunkLinearization(depgraph, read_lin);
auto cmp = CompareChunks(last_diagram, read_diagram); auto cmp = CompareChunks(last_diagram, read_diagram);
assert(cmp >= 0); assert(cmp >= 0);
if (cmp == 0) assert(last_diagram.size() >= read_diagram.size());
} }
} }
@@ -1052,12 +1073,9 @@ FUZZ_TARGET(clusterlin_linearize)
// SimpleLinearize is broken). // SimpleLinearize is broken).
if (simple_optimal) assert(cmp == 0); if (simple_optimal) assert(cmp == 0);
// Temporarily disabled, as Linearize() currently does not guarantee minimal chunks, even // If simple_chunking is diagram-optimal, it cannot have more chunks than chunking (as
// when it reports an optimal result. This will be re-introduced in a later commit. // chunking is claimed to be optimal, which implies minimal chunks).
// if (cmp == 0) assert(chunking.size() >= simple_chunking.size());
// // If simple_chunking is diagram-optimal, it cannot have more chunks than chunking (as
// // chunking is claimed to be optimal, which implies minimal chunks).
// if (cmp == 0) assert(chunking.size() >= simple_chunking.size());
// Compare with a linearization read from the fuzz input. // Compare with a linearization read from the fuzz input.
auto read = ReadLinearization(depgraph, reader); auto read = ReadLinearization(depgraph, reader);
+8 -8
View File
@@ -402,14 +402,14 @@ inline uint64_t MaxOptimalLinearizationIters(DepGraphIndex cluster_count)
// *some* reasonable cost bound, optimal linearizations are always found. // *some* reasonable cost bound, optimal linearizations are always found.
static constexpr uint64_t ITERS[65] = { static constexpr uint64_t ITERS[65] = {
0, 0,
0, 2, 8, 21, 51, 96, 162, 200, 0, 4, 10, 34, 76, 118, 184, 225,
273, 323, 413, 506, 602, 788, 883, 948, 320, 376, 464, 573, 830, 868, 1019, 1468,
1153, 1187, 1367, 1619, 1854, 2271, 2257, 2707, 1375, 1785, 1880, 1854, 2551, 2559, 4336, 4784,
2904, 3275, 3342, 4209, 4648, 4146, 4273, 4905, 5547, 5807, 6157, 6075, 6961, 7403, 7756, 8001,
5358, 5767, 5977, 6777, 7812, 7689, 8478, 8425, 8041, 7579, 8483, 10077, 9015, 9388, 9626, 12371,
9561, 11765, 10743, 11806, 12812, 12838, 15421, 16778, 12847, 12102, 15173, 15800, 20319, 22190, 23183, 24361,
16661, 19393, 17995, 23947, 23314, 24564, 26209, 29267, 24909, 19225, 27419, 23789, 25909, 21993, 25596, 24130,
24719, 31065, 31794, 29185, 32465, 35432, 39986, 36865 26349, 31823, 31855, 31250, 32688, 34825, 41710, 45478
}; };
assert(cluster_count < std::size(ITERS)); assert(cluster_count < std::size(ITERS));
// Multiply the table number by two, to account for the fact that they are not absolutes. // Multiply the table number by two, to account for the fact that they are not absolutes.
+1 -2
View File
@@ -2063,8 +2063,7 @@ std::pair<uint64_t, bool> GenericClusterImpl::Relinearize(TxGraphImpl& graph, in
uint64_t rng_seed = graph.m_rng.rand64(); uint64_t rng_seed = graph.m_rng.rand64();
auto [linearization, optimal, cost] = Linearize(m_depgraph, max_iters, rng_seed, m_linearization, /*is_topological=*/IsTopological()); auto [linearization, optimal, cost] = Linearize(m_depgraph, max_iters, rng_seed, m_linearization, /*is_topological=*/IsTopological());
// Postlinearize to undo some of the non-determinism caused by randomizing the linearization. // Postlinearize to undo some of the non-determinism caused by randomizing the linearization.
// This also guarantees that all chunks are connected (which is not guaranteed by SFL // This also guarantees that all chunks are connected (even when non-optimal).
// currently, even when optimal).
PostLinearize(m_depgraph, linearization); PostLinearize(m_depgraph, linearization);
// Update the linearization. // Update the linearization.
m_linearization = std::move(linearization); m_linearization = std::move(linearization);