|
11 | 11 | #include <util/bitset.h>
|
12 | 12 | #include <util/feefrac.h>
|
13 | 13 |
|
| 14 | +#include <algorithm> |
14 | 15 | #include <stdint.h>
|
15 | 16 | #include <vector>
|
16 | 17 | #include <utility>
|
@@ -140,6 +141,29 @@ class ExhaustiveCandidateFinder
|
140 | 141 | }
|
141 | 142 | };
|
142 | 143 |
|
| 144 | +/** A simple linearization algorithm. |
| 145 | + * |
| 146 | + * This matches Linearize() in interface and behavior, though with fewer optimizations, and using |
| 147 | + * just SimpleCandidateFinder rather than AncestorCandidateFinder and SearchCandidateFinder. |
| 148 | + */ |
| 149 | +template<typename SetType> |
| 150 | +std::pair<std::vector<ClusterIndex>, bool> SimpleLinearize(const DepGraph<SetType>& depgraph, uint64_t max_iterations) |
| 151 | +{ |
| 152 | + std::vector<ClusterIndex> linearization; |
| 153 | + SimpleCandidateFinder finder(depgraph); |
| 154 | + SetType todo = SetType::Fill(depgraph.TxCount()); |
| 155 | + bool optimal = true; |
| 156 | + while (todo.Any()) { |
| 157 | + auto [candidate, iterations_done] = finder.FindCandidateSet(max_iterations); |
| 158 | + if (iterations_done == max_iterations) optimal = false; |
| 159 | + depgraph.AppendTopo(linearization, candidate.transactions); |
| 160 | + todo -= candidate.transactions; |
| 161 | + finder.MarkDone(candidate.transactions); |
| 162 | + max_iterations -= iterations_done; |
| 163 | + } |
| 164 | + return {std::move(linearization), optimal}; |
| 165 | +} |
| 166 | + |
143 | 167 | /** Given a dependency graph, and a todo set, read a topological subset of todo from reader. */
|
144 | 168 | template<typename SetType>
|
145 | 169 | SetType ReadTopologicalSet(const DepGraph<SetType>& depgraph, const SetType& todo, SpanReader& reader)
|
@@ -458,3 +482,68 @@ FUZZ_TARGET(clusterlin_search_finder)
|
458 | 482 | assert(exh_finder.AllDone());
|
459 | 483 | assert(anc_finder.AllDone());
|
460 | 484 | }
|
| 485 | + |
| 486 | +FUZZ_TARGET(clusterlin_linearize) |
| 487 | +{ |
| 488 | + // Verify the behavior of Linearize(). |
| 489 | + |
| 490 | + // Retrieve an iteration count, and a depgraph from the fuzz input. |
| 491 | + SpanReader reader(buffer); |
| 492 | + DepGraph<TestBitSet> depgraph; |
| 493 | + uint64_t iter_count{0}; |
| 494 | + try { |
| 495 | + reader >> VARINT(iter_count) >> Using<DepGraphFormatter>(depgraph); |
| 496 | + } catch (const std::ios_base::failure&) {} |
| 497 | + |
| 498 | + // Invoke Linearize(). |
| 499 | + iter_count &= 0x7ffff; |
| 500 | + auto [linearization, optimal] = Linearize(depgraph, iter_count); |
| 501 | + SanityCheck(depgraph, linearization); |
| 502 | + auto chunking = ChunkLinearization(depgraph, linearization); |
| 503 | + |
| 504 | + // If the iteration count is sufficiently high, an optimal linearization must be found. |
| 505 | + // Each linearization step can use up to 2^k iterations, with steps k=1..n. That sum is |
| 506 | + // 2 * (2^n - 1) |
| 507 | + const uint64_t n = depgraph.TxCount(); |
| 508 | + if (n <= 18 && iter_count > 2U * ((uint64_t{1} << n) - 1U)) { |
| 509 | + assert(optimal); |
| 510 | + } |
| 511 | + |
| 512 | + // If Linearize claims optimal result, run quality tests. |
| 513 | + if (optimal) { |
| 514 | + // It must be as good as SimpleLinearize. |
| 515 | + auto [simple_linearization, simple_optimal] = SimpleLinearize(depgraph, MAX_SIMPLE_ITERATIONS); |
| 516 | + SanityCheck(depgraph, simple_linearization); |
| 517 | + auto simple_chunking = ChunkLinearization(depgraph, simple_linearization); |
| 518 | + auto cmp = CompareChunks(chunking, simple_chunking); |
| 519 | + assert(cmp >= 0); |
| 520 | + // If SimpleLinearize finds the optimal result too, they must be equal (if not, |
| 521 | + // SimpleLinearize is broken). |
| 522 | + if (simple_optimal) assert(cmp == 0); |
| 523 | + |
| 524 | + // Only for very small clusters, test every topologically-valid permutation. |
| 525 | + if (depgraph.TxCount() <= 7) { |
| 526 | + std::vector<ClusterIndex> perm_linearization(depgraph.TxCount()); |
| 527 | + for (ClusterIndex i = 0; i < depgraph.TxCount(); ++i) perm_linearization[i] = i; |
| 528 | + // Iterate over all valid permutations. |
| 529 | + do { |
| 530 | + // Determine whether perm_linearization is topological. |
| 531 | + TestBitSet perm_done; |
| 532 | + bool perm_is_topo{true}; |
| 533 | + for (auto i : perm_linearization) { |
| 534 | + perm_done.Set(i); |
| 535 | + if (!depgraph.Ancestors(i).IsSubsetOf(perm_done)) { |
| 536 | + perm_is_topo = false; |
| 537 | + break; |
| 538 | + } |
| 539 | + } |
| 540 | + // If so, verify that the obtained linearization is as good as the permutation. |
| 541 | + if (perm_is_topo) { |
| 542 | + auto perm_chunking = ChunkLinearization(depgraph, perm_linearization); |
| 543 | + auto cmp = CompareChunks(chunking, perm_chunking); |
| 544 | + assert(cmp >= 0); |
| 545 | + } |
| 546 | + } while(std::next_permutation(perm_linearization.begin(), perm_linearization.end())); |
| 547 | + } |
| 548 | + } |
| 549 | +} |
0 commit comments