txgraph: track amount of work done in linearization (preparation)

This commit is contained in:
Pieter Wuille 2025-04-13 11:09:04 -04:00
parent eb5f4db166
commit b074308e6f
4 changed files with 30 additions and 22 deletions

View file

@ -229,8 +229,8 @@ void BenchLinearizeOptimally(benchmark::Bench& bench, const std::array<uint8_t,
reader >> Using<DepGraphFormatter>(depgraph);
uint64_t rng_seed = 0;
bench.run([&] {
auto res = Linearize(depgraph, /*max_iterations=*/10000000, rng_seed++);
assert(res.second);
auto [_lin, optimal, _cost] = Linearize(depgraph, /*max_iterations=*/10000000, rng_seed++);
assert(optimal);
});
};

View file

@ -1030,19 +1030,20 @@ public:
* linearize.
* @param[in] old_linearization An existing linearization for the cluster (which must be
* topologically valid), or empty.
* @return A pair of:
* @return A tuple of:
* - The resulting linearization. It is guaranteed to be at least as
* good (in the feerate diagram sense) as old_linearization.
* - A boolean indicating whether the result is guaranteed to be
* optimal.
* - How many optimization steps were actually performed.
*
* Complexity: possibly O(N * min(max_iterations + N, sqrt(2^N))) where N=depgraph.TxCount().
*/
template<typename SetType>
std::pair<std::vector<DepGraphIndex>, bool> Linearize(const DepGraph<SetType>& depgraph, uint64_t max_iterations, uint64_t rng_seed, std::span<const DepGraphIndex> old_linearization = {}) noexcept
std::tuple<std::vector<DepGraphIndex>, bool, uint64_t> Linearize(const DepGraph<SetType>& depgraph, uint64_t max_iterations, uint64_t rng_seed, std::span<const DepGraphIndex> old_linearization = {}) noexcept
{
Assume(old_linearization.empty() || old_linearization.size() == depgraph.TxCount());
if (depgraph.TxCount() == 0) return {{}, true};
if (depgraph.TxCount() == 0) return {{}, true, 0};
uint64_t iterations_left = max_iterations;
std::vector<DepGraphIndex> linearization;
@ -1113,7 +1114,7 @@ std::pair<std::vector<DepGraphIndex>, bool> Linearize(const DepGraph<SetType>& d
}
}
return {std::move(linearization), optimal};
return {std::move(linearization), optimal, max_iterations - iterations_left};
}
/** Improve a given linearization.

View file

@ -906,7 +906,8 @@ FUZZ_TARGET(clusterlin_linearize)
// Invoke Linearize().
iter_count &= 0x7ffff;
auto [linearization, optimal] = Linearize(depgraph, iter_count, rng_seed, old_linearization);
auto [linearization, optimal, cost] = Linearize(depgraph, iter_count, rng_seed, old_linearization);
assert(cost <= iter_count);
SanityCheck(depgraph, linearization);
auto chunking = ChunkLinearization(depgraph, linearization);
@ -1090,7 +1091,7 @@ FUZZ_TARGET(clusterlin_postlinearize_tree)
// Try to find an even better linearization directly. This must not change the diagram for the
// same reason.
auto [opt_linearization, _optimal] = Linearize(depgraph_tree, 100000, rng_seed, post_linearization);
auto [opt_linearization, _optimal, _cost] = Linearize(depgraph_tree, 100000, rng_seed, post_linearization);
auto opt_chunking = ChunkLinearization(depgraph_tree, opt_linearization);
auto cmp_opt = CompareChunks(opt_chunking, post_chunking);
assert(cmp_opt == 0);

View file

@ -181,8 +181,8 @@ public:
void Merge(TxGraphImpl& graph, Cluster& cluster) noexcept;
/** Given a span of (parent, child) pairs that all belong to this Cluster, apply them. */
void ApplyDependencies(TxGraphImpl& graph, std::span<std::pair<GraphIndex, GraphIndex>> to_apply) noexcept;
/** Improve the linearization of this Cluster. */
void Relinearize(TxGraphImpl& graph, uint64_t max_iters) noexcept;
/** Improve the linearization of this Cluster. Returns how much work was performed. */
uint64_t Relinearize(TxGraphImpl& graph, uint64_t max_iters) noexcept;
/** For every chunk in the cluster, append its FeeFrac to ret. */
void AppendChunkFeerates(std::vector<FeeFrac>& ret) const noexcept;
/** Add a TrimTxData entry for every transaction in the Cluster to ret. Implicit dependencies
@ -561,10 +561,11 @@ public:
void Merge(std::span<Cluster*> to_merge) noexcept;
/** Apply all m_deps_to_add to the relevant Clusters in the specified level. */
void ApplyDependencies(int level) noexcept;
/** Make a specified Cluster have quality ACCEPTABLE or OPTIMAL. */
void MakeAcceptable(Cluster& cluster) noexcept;
/** Make all Clusters at the specified level have quality ACCEPTABLE or OPTIMAL. */
void MakeAllAcceptable(int level) noexcept;
/** Make a specified Cluster have quality ACCEPTABLE or OPTIMAL. Return how much work was performed. */
uint64_t MakeAcceptable(Cluster& cluster) noexcept;
/** Make all Clusters at the specified level have quality ACCEPTABLE or OPTIMAL. Return how much
* was performed. */
uint64_t MakeAllAcceptable(int level) noexcept;
// Implementations for the public TxGraph interface.
@ -1643,15 +1644,15 @@ void TxGraphImpl::ApplyDependencies(int level) noexcept
clusterset.m_group_data = GroupData{};
}
void Cluster::Relinearize(TxGraphImpl& graph, uint64_t max_iters) noexcept
uint64_t Cluster::Relinearize(TxGraphImpl& graph, uint64_t max_iters) noexcept
{
// We can only relinearize Clusters that do not need splitting.
Assume(!NeedsSplitting());
// No work is required for Clusters which are already optimally linearized.
if (IsOptimal()) return;
if (IsOptimal()) return 0;
// Invoke the actual linearization algorithm (passing in the existing one).
uint64_t rng_seed = graph.m_rng.rand64();
auto [linearization, optimal] = Linearize(m_depgraph, max_iters, rng_seed, m_linearization);
auto [linearization, optimal, cost] = Linearize(m_depgraph, max_iters, rng_seed, m_linearization);
// Postlinearize if the result isn't optimal already. This guarantees (among other things)
// that the chunks of the resulting linearization are all connected.
if (!optimal) PostLinearize(m_depgraph, linearization);
@ -1662,25 +1663,30 @@ void Cluster::Relinearize(TxGraphImpl& graph, uint64_t max_iters) noexcept
graph.SetClusterQuality(m_level, m_quality, m_setindex, new_quality);
// Update the Entry objects.
Updated(graph);
return cost;
}
void TxGraphImpl::MakeAcceptable(Cluster& cluster) noexcept
uint64_t TxGraphImpl::MakeAcceptable(Cluster& cluster) noexcept
{
uint64_t cost{0};
// Relinearize the Cluster if needed.
if (!cluster.NeedsSplitting() && !cluster.IsAcceptable() && !cluster.IsOversized()) {
cluster.Relinearize(*this, 10000);
cost += cluster.Relinearize(*this, 10000);
}
return cost;
}
void TxGraphImpl::MakeAllAcceptable(int level) noexcept
uint64_t TxGraphImpl::MakeAllAcceptable(int level) noexcept
{
ApplyDependencies(level);
auto& clusterset = GetClusterSet(level);
if (clusterset.m_oversized == true) return;
if (clusterset.m_oversized == true) return 0;
auto& queue = clusterset.m_clusters[int(QualityLevel::NEEDS_RELINEARIZE)];
uint64_t cost{0};
while (!queue.empty()) {
MakeAcceptable(*queue.back().get());
cost += MakeAcceptable(*queue.back().get());
}
return cost;
}
Cluster::Cluster(uint64_t sequence) noexcept : m_sequence{sequence} {}