Skip to content

Commit b64e61d

Browse files
committed
clusterlin: abstract try-permutations into ExhaustiveLinearize function
Rather than this exhaustive linearization check happening inline inside clusterlin_simple_linearize, abstract it out into a Linearize()-like function for clarity. Note that this isn't exactly a refactor, because the old code would compare the found linearization against all (valid) permutations, while the new code instead first computes the best linearization from all valid permutations, and then compares it with the found one.
1 parent 1fa55a6 commit b64e61d

File tree

1 file changed

+57
-32
lines changed

1 file changed

+57
-32
lines changed

src/test/fuzz/cluster_linearize.cpp

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,58 @@ std::pair<std::vector<DepGraphIndex>, bool> SimpleLinearize(const DepGraph<SetTy
168168
return {std::move(linearization), optimal};
169169
}
170170

171+
/** An even simpler linearization algorithm that tries all permutations.
172+
*
173+
* This roughly matches SimpleLinearize() (and Linearize) in interface and behavior, but always
174+
* tries all topologically-valid transaction orderings, has no way to bound how much work it does,
175+
* and always finds the optimal. With an O(n!) complexity, it should only be used for small
176+
* clusters.
177+
*/
178+
template<typename SetType>
179+
std::vector<DepGraphIndex> ExhaustiveLinearize(const DepGraph<SetType>& depgraph)
180+
{
181+
// The best linearization so far, and its chunking.
182+
std::vector<DepGraphIndex> linearization;
183+
std::vector<FeeFrac> chunking;
184+
185+
std::vector<DepGraphIndex> perm_linearization;
186+
// Initialize with the lexicographically-first linearization.
187+
for (DepGraphIndex i : depgraph.Positions()) perm_linearization.push_back(i);
188+
// Iterate over all valid permutations.
189+
do {
190+
/** What prefix of perm_linearization is topological. */
191+
DepGraphIndex topo_length{0};
192+
TestBitSet perm_done;
193+
while (topo_length < perm_linearization.size()) {
194+
auto i = perm_linearization[topo_length];
195+
perm_done.Set(i);
196+
if (!depgraph.Ancestors(i).IsSubsetOf(perm_done)) break;
197+
++topo_length;
198+
}
199+
if (topo_length == perm_linearization.size()) {
200+
// If all of perm_linearization is topological, check if it is perhaps our best
201+
// linearization so far.
202+
auto perm_chunking = ChunkLinearization(depgraph, perm_linearization);
203+
auto cmp = CompareChunks(perm_chunking, chunking);
204+
// If the diagram is better, or if it is equal but with more chunks (because we
205+
// prefer minimal chunks), consider this better.
206+
if (linearization.empty() || cmp > 0 || (cmp == 0 && perm_chunking.size() > chunking.size())) {
207+
linearization = perm_linearization;
208+
chunking = perm_chunking;
209+
}
210+
} else {
211+
// Otherwise, fast forward to the last permutation with the same non-topological
212+
// prefix.
213+
auto first_non_topo = perm_linearization.begin() + topo_length;
214+
assert(std::is_sorted(first_non_topo + 1, perm_linearization.end()));
215+
std::reverse(first_non_topo + 1, perm_linearization.end());
216+
}
217+
} while(std::next_permutation(perm_linearization.begin(), perm_linearization.end()));
218+
219+
return linearization;
220+
}
221+
222+
171223
/** Stitch connected components together in a DepGraph, guaranteeing its corresponding cluster is connected. */
172224
template<typename BS>
173225
void MakeConnected(DepGraph<BS>& depgraph)
@@ -996,38 +1048,11 @@ FUZZ_TARGET(clusterlin_simple_linearize)
9961048
// If SimpleLinearize claims optimal result, and the cluster is sufficiently small (there are
9971049
// n! linearizations), test that the result is as good as every valid linearization.
9981050
if (optimal && depgraph.TxCount() <= 8) {
999-
std::vector<DepGraphIndex> perm_linearization;
1000-
// Initialize with the lexicographically-first linearization.
1001-
for (DepGraphIndex i : depgraph.Positions()) perm_linearization.push_back(i);
1002-
// Iterate over all valid permutations.
1003-
do {
1004-
/** What prefix of perm_linearization is topological. */
1005-
DepGraphIndex topo_length{0};
1006-
TestBitSet perm_done;
1007-
while (topo_length < perm_linearization.size()) {
1008-
auto i = perm_linearization[topo_length];
1009-
perm_done.Set(i);
1010-
if (!depgraph.Ancestors(i).IsSubsetOf(perm_done)) break;
1011-
++topo_length;
1012-
}
1013-
if (topo_length == perm_linearization.size()) {
1014-
// If all of perm_linearization is topological, verify that the obtained
1015-
// linearization is no worse than it.
1016-
auto perm_chunking = ChunkLinearization(depgraph, perm_linearization);
1017-
auto cmp = CompareChunks(simple_chunking, perm_chunking);
1018-
assert(cmp >= 0);
1019-
// If perm_chunking is diagram-optimal, it cannot have more chunks than
1020-
// simple_chunking (as simple_chunking claims to be optimal, which implies minimal
1021-
// chunks.
1022-
if (cmp == 0) assert(simple_chunking.size() >= perm_chunking.size());
1023-
} else {
1024-
// Otherwise, fast forward to the last permutation with the same non-topological
1025-
// prefix.
1026-
auto first_non_topo = perm_linearization.begin() + topo_length;
1027-
assert(std::is_sorted(first_non_topo + 1, perm_linearization.end()));
1028-
std::reverse(first_non_topo + 1, perm_linearization.end());
1029-
}
1030-
} while(std::next_permutation(perm_linearization.begin(), perm_linearization.end()));
1051+
auto exh_linearization = ExhaustiveLinearize(depgraph);
1052+
auto exh_chunking = ChunkLinearization(depgraph, exh_linearization);
1053+
auto cmp = CompareChunks(simple_chunking, exh_chunking);
1054+
assert(cmp == 0);
1055+
assert(simple_chunking.size() == exh_chunking.size());
10311056
}
10321057

10331058
if (optimal) {

0 commit comments

Comments
 (0)