Skip to content

Commit 75b5d42

Browse files
sipainstagibbs
andcommitted
clusterlin: make DepGraph::AddDependency support multiple dependencies at once
This changes DepGraph::AddDependency into DepGraph::AddDependencies, which takes in a single child, but a set of parent transactions, making them all dependencies at once. This is important for performance. N transactions can have O(N^2) parents combined, so constructing a full DepGraph using just AddDependency (which is O(N) on its own) could take O(N^3) time, while doing the same with AddDependencies (also O(N) on its own) only takes O(N^2). Notably, this matters for DepGraphFormatter::Unser, which goes from O(N^3) to O(N^2). Co-Authored-By: Greg Sanders <gsanders87@gmail.com>
1 parent abf5064 commit 75b5d42

File tree

4 files changed

+96
-80
lines changed

4 files changed

+96
-80
lines changed

src/bench/cluster_linearize.cpp

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ DepGraph<SetType> MakeLinearGraph(ClusterIndex ntx)
2828
DepGraph<SetType> depgraph;
2929
for (ClusterIndex i = 0; i < ntx; ++i) {
3030
depgraph.AddTransaction({-int32_t(i), 1});
31-
if (i > 0) depgraph.AddDependency(i - 1, i);
31+
if (i > 0) depgraph.AddDependencies(SetType::Singleton(i - 1), i);
3232
}
3333
return depgraph;
3434
}
@@ -43,7 +43,7 @@ DepGraph<SetType> MakeWideGraph(ClusterIndex ntx)
4343
DepGraph<SetType> depgraph;
4444
for (ClusterIndex i = 0; i < ntx; ++i) {
4545
depgraph.AddTransaction({int32_t(i) + 1, 1});
46-
if (i > 0) depgraph.AddDependency(0, i);
46+
if (i > 0) depgraph.AddDependencies(SetType::Singleton(0), i);
4747
}
4848
return depgraph;
4949
}
@@ -70,19 +70,19 @@ DepGraph<SetType> MakeHardGraph(ClusterIndex ntx)
7070
depgraph.AddTransaction({1, 2});
7171
} else if (i == 1) {
7272
depgraph.AddTransaction({14, 2});
73-
depgraph.AddDependency(0, 1);
73+
depgraph.AddDependencies(SetType::Singleton(0), 1);
7474
} else if (i == 2) {
7575
depgraph.AddTransaction({6, 1});
76-
depgraph.AddDependency(2, 1);
76+
depgraph.AddDependencies(SetType::Singleton(2), 1);
7777
} else if (i == 3) {
7878
depgraph.AddTransaction({5, 1});
79-
depgraph.AddDependency(2, 3);
79+
depgraph.AddDependencies(SetType::Singleton(2), 3);
8080
} else if ((i & 1) == 0) {
8181
depgraph.AddTransaction({7, 1});
82-
depgraph.AddDependency(i - 1, i);
82+
depgraph.AddDependencies(SetType::Singleton(i - 1), i);
8383
} else {
8484
depgraph.AddTransaction({5, 1});
85-
depgraph.AddDependency(i, 4);
85+
depgraph.AddDependencies(SetType::Singleton(i), 4);
8686
}
8787
} else {
8888
// Even cluster size.
@@ -98,16 +98,16 @@ DepGraph<SetType> MakeHardGraph(ClusterIndex ntx)
9898
depgraph.AddTransaction({1, 1});
9999
} else if (i == 1) {
100100
depgraph.AddTransaction({3, 1});
101-
depgraph.AddDependency(0, 1);
101+
depgraph.AddDependencies(SetType::Singleton(0), 1);
102102
} else if (i == 2) {
103103
depgraph.AddTransaction({1, 1});
104-
depgraph.AddDependency(0, 2);
104+
depgraph.AddDependencies(SetType::Singleton(0), 2);
105105
} else if (i & 1) {
106106
depgraph.AddTransaction({4, 1});
107-
depgraph.AddDependency(i - 1, i);
107+
depgraph.AddDependencies(SetType::Singleton(i - 1), i);
108108
} else {
109109
depgraph.AddTransaction({0, 1});
110-
depgraph.AddDependency(i, 3);
110+
depgraph.AddDependencies(SetType::Singleton(i), 3);
111111
}
112112
}
113113
}
@@ -195,7 +195,7 @@ void BenchMergeLinearizationsWorstCase(ClusterIndex ntx, benchmark::Bench& bench
195195
DepGraph<SetType> depgraph;
196196
for (ClusterIndex i = 0; i < ntx; ++i) {
197197
depgraph.AddTransaction({i, 1});
198-
if (i) depgraph.AddDependency(0, i);
198+
if (i) depgraph.AddDependencies(SetType::Singleton(0), i);
199199
}
200200
std::vector<ClusterIndex> lin1;
201201
std::vector<ClusterIndex> lin2;

src/cluster_linearize.h

Lines changed: 24 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -86,57 +86,30 @@ class DepGraph
8686
*
8787
* Complexity: O(N^2) where N=cluster.size().
8888
*/
89-
explicit DepGraph(const Cluster<SetType>& cluster) noexcept : entries(cluster.size())
89+
explicit DepGraph(const Cluster<SetType>& cluster) noexcept : DepGraph(cluster.size())
9090
{
9191
for (ClusterIndex i = 0; i < cluster.size(); ++i) {
9292
// Fill in fee and size.
9393
entries[i].feerate = cluster[i].first;
94-
// Fill in direct parents as ancestors.
95-
entries[i].ancestors = cluster[i].second;
96-
// Make sure transactions are ancestors of themselves.
97-
entries[i].ancestors.Set(i);
98-
}
99-
100-
// Propagate ancestor information.
101-
for (ClusterIndex i = 0; i < entries.size(); ++i) {
102-
// At this point, entries[a].ancestors[b] is true iff b is an ancestor of a and there
103-
// is a path from a to b through the subgraph consisting of {a, b} union
104-
// {0, 1, ..., (i-1)}.
105-
SetType to_merge = entries[i].ancestors;
106-
for (ClusterIndex j = 0; j < entries.size(); ++j) {
107-
if (entries[j].ancestors[i]) {
108-
entries[j].ancestors |= to_merge;
109-
}
110-
}
111-
}
112-
113-
// Fill in descendant information by transposing the ancestor information.
114-
for (ClusterIndex i = 0; i < entries.size(); ++i) {
115-
for (auto j : entries[i].ancestors) {
116-
entries[j].descendants.Set(i);
117-
}
94+
// Fill in dependencies.
95+
AddDependencies(cluster[i].second, i);
11896
}
11997
}
12098

12199
/** Construct a DepGraph object given another DepGraph and a mapping from old to new.
122100
*
123101
* Complexity: O(N^2) where N=depgraph.TxCount().
124102
*/
125-
DepGraph(const DepGraph<SetType>& depgraph, Span<const ClusterIndex> mapping) noexcept : entries(depgraph.TxCount())
103+
DepGraph(const DepGraph<SetType>& depgraph, Span<const ClusterIndex> mapping) noexcept : DepGraph(depgraph.TxCount())
126104
{
127105
Assert(mapping.size() == depgraph.TxCount());
128-
// Fill in fee, size, ancestors.
129106
for (ClusterIndex i = 0; i < depgraph.TxCount(); ++i) {
130-
const auto& input = depgraph.entries[i];
131-
auto& output = entries[mapping[i]];
132-
output.feerate = input.feerate;
133-
for (auto j : input.ancestors) output.ancestors.Set(mapping[j]);
134-
}
135-
// Fill in descendant information.
136-
for (ClusterIndex i = 0; i < entries.size(); ++i) {
137-
for (auto j : entries[i].ancestors) {
138-
entries[j].descendants.Set(i);
139-
}
107+
// Fill in fee and size.
108+
entries[mapping[i]].feerate = depgraph.entries[i].feerate;
109+
// Fill in dependencies by mapping direct parents.
110+
SetType parents;
111+
for (auto j : depgraph.GetReducedParents(i)) parents.Set(mapping[j]);
112+
AddDependencies(parents, mapping[i]);
140113
}
141114
}
142115

@@ -164,21 +137,26 @@ class DepGraph
164137
return new_idx;
165138
}
166139

167-
/** Modify this transaction graph, adding a dependency between a specified parent and child.
140+
/** Modify this transaction graph, adding multiple parents to a specified child.
168141
*
169142
* Complexity: O(N) where N=TxCount().
170-
**/
171-
void AddDependency(ClusterIndex parent, ClusterIndex child) noexcept
143+
*/
144+
void AddDependencies(const SetType& parents, ClusterIndex child) noexcept
172145
{
173-
// Bail out if dependency is already implied.
174-
if (entries[child].ancestors[parent]) return;
175-
// To each ancestor of the parent, add as descendants the descendants of the child.
146+
// Compute the ancestors of parents that are not already ancestors of child.
147+
SetType par_anc;
148+
for (auto par : parents - Ancestors(child)) {
149+
par_anc |= Ancestors(par);
150+
}
151+
par_anc -= Ancestors(child);
152+
// Bail out if there are no such ancestors.
153+
if (par_anc.None()) return;
154+
// To each such ancestor, add as descendants the descendants of the child.
176155
const auto& chl_des = entries[child].descendants;
177-
for (auto anc_of_par : Ancestors(parent)) {
156+
for (auto anc_of_par : par_anc) {
178157
entries[anc_of_par].descendants |= chl_des;
179158
}
180-
// To each descendant of the child, add as ancestors the ancestors of the parent.
181-
const auto& par_anc = entries[parent].ancestors;
159+
// To each descendant of the child, add those ancestors.
182160
for (auto dec_of_chl : Descendants(child)) {
183161
entries[dec_of_chl].ancestors |= par_anc;
184162
}

src/test/fuzz/cluster_linearize.cpp

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#include <cluster_linearize.h>
6+
#include <random.h>
67
#include <serialize.h>
78
#include <streams.h>
89
#include <test/fuzz/fuzz.h>
@@ -176,7 +177,7 @@ void MakeConnected(DepGraph<BS>& depgraph)
176177
while (todo.Any()) {
177178
auto nextcomp = depgraph.FindConnectedComponent(todo);
178179
Assume(depgraph.IsConnected(nextcomp));
179-
depgraph.AddDependency(comp.Last(), nextcomp.First());
180+
depgraph.AddDependencies(BS::Singleton(comp.Last()), nextcomp.First());
180181
todo -= nextcomp;
181182
comp = nextcomp;
182183
}
@@ -240,32 +241,65 @@ std::vector<ClusterIndex> ReadLinearization(const DepGraph<BS>& depgraph, SpanRe
240241

241242
} // namespace
242243

243-
FUZZ_TARGET(clusterlin_add_dependency)
244+
FUZZ_TARGET(clusterlin_add_dependencies)
244245
{
245-
// Verify that computing a DepGraph from a cluster, or building it step by step using AddDependency
246-
// have the same effect.
246+
// Verify that computing a DepGraph from a cluster, or building it step by step using
247+
// AddDependencies has the same effect.
247248

248-
// Construct a cluster of a certain length, with no dependencies.
249249
FuzzedDataProvider provider(buffer.data(), buffer.size());
250-
auto num_tx = provider.ConsumeIntegralInRange<ClusterIndex>(2, 32);
250+
auto rng_seed = provider.ConsumeIntegral<uint64_t>();
251+
InsecureRandomContext rng(rng_seed);
252+
253+
// Construct a cluster of a certain length, with no dependencies.
254+
auto num_tx = provider.ConsumeIntegralInRange<ClusterIndex>(2, TestBitSet::Size());
251255
Cluster<TestBitSet> cluster(num_tx, std::pair{FeeFrac{0, 1}, TestBitSet{}});
252256
// Construct the corresponding DepGraph object (also no dependencies).
253-
DepGraph depgraph(cluster);
254-
SanityCheck(depgraph);
255-
// Read (parent, child) pairs, and add them to the cluster and depgraph.
256-
LIMITED_WHILE(provider.remaining_bytes() > 0, TestBitSet::Size() * TestBitSet::Size()) {
257-
auto parent = provider.ConsumeIntegralInRange<ClusterIndex>(0, num_tx - 1);
258-
auto child = provider.ConsumeIntegralInRange<ClusterIndex>(0, num_tx - 2);
259-
child += (child >= parent);
260-
cluster[child].second.Set(parent);
261-
depgraph.AddDependency(parent, child);
262-
assert(depgraph.Ancestors(child)[parent]);
263-
assert(depgraph.Descendants(parent)[child]);
257+
DepGraph depgraph_batch(cluster);
258+
SanityCheck(depgraph_batch);
259+
// Read (parents, child) pairs, and add the dependencies to the cluster and depgraph.
260+
std::vector<std::pair<ClusterIndex, ClusterIndex>> deps_list;
261+
LIMITED_WHILE(provider.remaining_bytes() > 0, TestBitSet::Size()) {
262+
const auto parents_mask = provider.ConsumeIntegralInRange<uint64_t>(0, (uint64_t{1} << num_tx) - 1);
263+
auto child = provider.ConsumeIntegralInRange<ClusterIndex>(0, num_tx - 1);
264+
265+
auto parents_mask_shifted = parents_mask;
266+
TestBitSet deps;
267+
for (ClusterIndex i = 0; i < num_tx; ++i) {
268+
if (parents_mask_shifted & 1) {
269+
deps.Set(i);
270+
cluster[child].second.Set(i);
271+
}
272+
parents_mask_shifted >>= 1;
273+
}
274+
assert(parents_mask_shifted == 0);
275+
depgraph_batch.AddDependencies(deps, child);
276+
for (auto i : deps) {
277+
deps_list.emplace_back(i, child);
278+
assert(depgraph_batch.Ancestors(child)[i]);
279+
assert(depgraph_batch.Descendants(i)[child]);
280+
}
264281
}
265282
// Sanity check the result.
266-
SanityCheck(depgraph);
283+
SanityCheck(depgraph_batch);
267284
// Verify that the resulting DepGraph matches one recomputed from the cluster.
268-
assert(DepGraph(cluster) == depgraph);
285+
assert(DepGraph(cluster) == depgraph_batch);
286+
287+
DepGraph<TestBitSet> depgraph_individual;
288+
// Add all transactions to depgraph_individual.
289+
for (const auto& [feerate, parents] : cluster) {
290+
depgraph_individual.AddTransaction(feerate);
291+
}
292+
SanityCheck(depgraph_individual);
293+
// Add all individual dependencies to depgraph_individual in randomized order.
294+
std::shuffle(deps_list.begin(), deps_list.end(), rng);
295+
for (auto [parent, child] : deps_list) {
296+
depgraph_individual.AddDependencies(TestBitSet::Singleton(parent), child);
297+
assert(depgraph_individual.Ancestors(child)[parent]);
298+
assert(depgraph_individual.Descendants(parent)[child]);
299+
}
300+
// Sanity check and compare again the batched version.
301+
SanityCheck(depgraph_individual);
302+
assert(depgraph_individual == depgraph_batch);
269303
}
270304

271305
FUZZ_TARGET(clusterlin_cluster_serialization)
@@ -897,12 +931,16 @@ FUZZ_TARGET(clusterlin_postlinearize_tree)
897931
if (direction & 1) {
898932
for (ClusterIndex i = 0; i < depgraph_gen.TxCount(); ++i) {
899933
auto children = depgraph_gen.GetReducedChildren(i);
900-
if (children.Any()) depgraph_tree.AddDependency(i, children.First());
934+
if (children.Any()) {
935+
depgraph_tree.AddDependencies(TestBitSet::Singleton(i), children.First());
936+
}
901937
}
902938
} else {
903939
for (ClusterIndex i = 0; i < depgraph_gen.TxCount(); ++i) {
904940
auto parents = depgraph_gen.GetReducedParents(i);
905-
if (parents.Any()) depgraph_tree.AddDependency(parents.First(), i);
941+
if (parents.Any()) {
942+
depgraph_tree.AddDependencies(TestBitSet::Singleton(parents.First()), i);
943+
}
906944
}
907945
}
908946

src/test/util/cluster_linearize.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ struct DepGraphFormatter
234234
if (new_feerate.IsEmpty()) break;
235235
assert(reordering.size() < SetType::Size());
236236
auto topo_idx = topo_depgraph.AddTransaction(new_feerate);
237-
for (auto parent : new_ancestors) topo_depgraph.AddDependency(parent, topo_idx);
237+
topo_depgraph.AddDependencies(new_ancestors, topo_idx);
238238
diff %= total_size + 1;
239239
// Insert the new transaction at distance diff back from the end.
240240
for (auto& pos : reordering) {

0 commit comments

Comments
 (0)