Merge bitcoin/bitcoin#33157: cluster mempool: control/optimize TxGraph memory usage
023cd5a546txgraph: add SingletonClusterImpl (mem optimization) (Pieter Wuille)e346250732txgraph: give Clusters a range of intended tx counts (preparation) (Pieter Wuille)e93b0f09cctxgraph: abstract out creation of empty Clusters (refactor) (Pieter Wuille)6baf12621ftxgraph: comment fixes (doc fix) (Pieter Wuille)726b995739txgraph: make Cluster an abstract class (refactor) (Pieter Wuille)2602d89eddtxgraph: avoid accessing other Cluster internals (refactor) (Pieter Wuille)04c808ac4ctxgraph: expose memory usage estimate function (feature) (Pieter Wuille)7680bb8fd4txgraph: keep track of Cluster memory usage (preparation) (Pieter Wuille)4ba562e5f4txgraph: keep data structures compact (mem optimization) (Pieter Wuille)bb5cb222aedepgraph: add memory usage control (feature) (Pieter Wuille)b1637a90detxgraph: avoid holes in DepGraph positions (mem optimization) (Pieter Wuille)2b1d302508txgraph: move some sanity checks from Cluster to TxGraphImpl (refactor) (Pieter Wuille)d40302fbaftxgraph: Make level of Cluster implicit (optimization) (Pieter Wuille) Pull request description: Part of #30289. This adds a few optimizations to reduce `TxGraph`'s memory usage, and makes sure that dynamic memory it uses doesn't linger after shrinking clusters. Finally, it exposes a function `GetMainMemoryUsage()` to compute `TxGraph`'s approximate memory usage. It makes the `Cluster` type abstract, with two instances (`SingletonClusterImpl` for 1-transaction clusters, and `GenericClusterImpl` for others). On my 64-bit system, I obtain the following numbers: * `SingletonClusterImpl`: 48 bytes, plus 16 bytes malloc overhead in its `unique_ptr`, plus 8-byte pointer in `m_clusters` * `GenericClusterImpl`: 104 bytes, plus 16 bytes malloc overhead in its `unique_ptr`, plus 8-byte pointer in `m_clusters`, plus 72 bytes malloc overhead inside its vectors and `DepGraph`, plus 40 bytes per transaction in those. * `TxGraphImpl::Entry`: 72 bytes per transaction * `TxGraphImpl::ChunkData`: 8 bytes, plus 56 bytes in `std::set` overhead + malloc overhead, all per chunk. * `TxGraph::Ref`: 16 bytes per transaction This overall amounts to 200 bytes per cluster, plus 64 bytes per chunk, plus 128 bytes per transaction, but only 224 bytes overall per singleton cluster. ACKs for top commit: l0rinc: code review reACK023cd5a546instagibbs: reACK023cd5a546ismaelsadeeq: reACK023cd5a546🚢 glozow: reACK023cd5a546Tree-SHA512: c957b27f47318be7c25d71453df2ae9d4e7bf21dab13b6e5e975cca122a221a99b15c584872491225785d276a9165f090675ee0f4460a2775bd3271933e3b246
This commit is contained in:
5 files changed
+848
-262
No files matched your search
@@ -12,6 +12,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <memusage.h>
|
||||
#include <random.h>
|
||||
#include <span.h>
|
||||
#include <util/feefrac.h>
|
||||
@@ -332,6 +333,17 @@ public:
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Reduce memory usage if possible. No observable effect. */
|
||||
void Compact() noexcept
|
||||
{
|
||||
entries.shrink_to_fit();
|
||||
}
|
||||
|
||||
size_t DynamicMemoryUsage() const noexcept
|
||||
{
|
||||
return memusage::DynamicUsage(entries);
|
||||
}
|
||||
};
|
||||
|
||||
/** A set of transactions together with their aggregate feerate. */
|
||||
|
||||
@@ -452,63 +452,72 @@ FUZZ_TARGET(clusterlin_depgraph_sim)
|
||||
}
|
||||
};
|
||||
|
||||
auto last_compaction_pos{real.PositionRange()};
|
||||
|
||||
LIMITED_WHILE(provider.remaining_bytes() > 0, 1000) {
|
||||
uint8_t command = provider.ConsumeIntegral<uint8_t>();
|
||||
if (num_tx_sim == 0 || ((command % 3) <= 0 && num_tx_sim < TestBitSet::Size())) {
|
||||
// AddTransaction.
|
||||
auto fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
|
||||
auto size = provider.ConsumeIntegralInRange<int32_t>(1, 0x3fffff);
|
||||
FeeFrac feerate{fee, size};
|
||||
// Apply to DepGraph.
|
||||
auto idx = real.AddTransaction(feerate);
|
||||
// Verify that the returned index is correct.
|
||||
assert(!sim[idx].has_value());
|
||||
for (DepGraphIndex i = 0; i < TestBitSet::Size(); ++i) {
|
||||
if (!sim[i].has_value()) {
|
||||
assert(idx == i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Update sim.
|
||||
sim[idx] = {feerate, TestBitSet::Singleton(idx)};
|
||||
++num_tx_sim;
|
||||
continue;
|
||||
}
|
||||
if ((command % 3) <= 1 && num_tx_sim > 0) {
|
||||
// AddDependencies.
|
||||
DepGraphIndex child = idx_fn();
|
||||
auto parents = subset_fn();
|
||||
// Apply to DepGraph.
|
||||
real.AddDependencies(parents, child);
|
||||
// Apply to sim.
|
||||
sim[child]->second |= parents;
|
||||
continue;
|
||||
}
|
||||
if (num_tx_sim > 0) {
|
||||
// Remove transactions.
|
||||
auto del = set_fn();
|
||||
// Propagate all ancestry information before deleting anything in the simulation (as
|
||||
// intermediary transactions may be deleted which impact connectivity).
|
||||
anc_update_fn();
|
||||
// Compare the state of the transactions being deleted.
|
||||
for (auto i : del) check_fn(i);
|
||||
// Apply to DepGraph.
|
||||
real.RemoveTransactions(del);
|
||||
// Apply to sim.
|
||||
for (DepGraphIndex i = 0; i < sim.size(); ++i) {
|
||||
if (sim[i].has_value()) {
|
||||
if (del[i]) {
|
||||
--num_tx_sim;
|
||||
sim[i] = std::nullopt;
|
||||
} else {
|
||||
sim[i]->second -= del;
|
||||
int command = provider.ConsumeIntegral<uint8_t>() % 4;
|
||||
while (true) {
|
||||
// Iterate decreasing command until an applicable branch is found.
|
||||
if (num_tx_sim < TestBitSet::Size() && command-- == 0) {
|
||||
// AddTransaction.
|
||||
auto fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
|
||||
auto size = provider.ConsumeIntegralInRange<int32_t>(1, 0x3fffff);
|
||||
FeeFrac feerate{fee, size};
|
||||
// Apply to DepGraph.
|
||||
auto idx = real.AddTransaction(feerate);
|
||||
// Verify that the returned index is correct.
|
||||
assert(!sim[idx].has_value());
|
||||
for (DepGraphIndex i = 0; i < TestBitSet::Size(); ++i) {
|
||||
if (!sim[i].has_value()) {
|
||||
assert(idx == i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Update sim.
|
||||
sim[idx] = {feerate, TestBitSet::Singleton(idx)};
|
||||
++num_tx_sim;
|
||||
break;
|
||||
} else if (num_tx_sim > 0 && command-- == 0) {
|
||||
// AddDependencies.
|
||||
DepGraphIndex child = idx_fn();
|
||||
auto parents = subset_fn();
|
||||
// Apply to DepGraph.
|
||||
real.AddDependencies(parents, child);
|
||||
// Apply to sim.
|
||||
sim[child]->second |= parents;
|
||||
break;
|
||||
} else if (num_tx_sim > 0 && command-- == 0) {
|
||||
// Remove transactions.
|
||||
auto del = set_fn();
|
||||
// Propagate all ancestry information before deleting anything in the simulation (as
|
||||
// intermediary transactions may be deleted which impact connectivity).
|
||||
anc_update_fn();
|
||||
// Compare the state of the transactions being deleted.
|
||||
for (auto i : del) check_fn(i);
|
||||
// Apply to DepGraph.
|
||||
real.RemoveTransactions(del);
|
||||
// Apply to sim.
|
||||
for (DepGraphIndex i = 0; i < sim.size(); ++i) {
|
||||
if (sim[i].has_value()) {
|
||||
if (del[i]) {
|
||||
--num_tx_sim;
|
||||
sim[i] = std::nullopt;
|
||||
} else {
|
||||
sim[i]->second -= del;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
} else if (command-- == 0) {
|
||||
// Compact.
|
||||
const size_t mem_before{real.DynamicMemoryUsage()};
|
||||
real.Compact();
|
||||
const size_t mem_after{real.DynamicMemoryUsage()};
|
||||
assert(real.PositionRange() < last_compaction_pos ? mem_after < mem_before : mem_after <= mem_before);
|
||||
last_compaction_pos = real.PositionRange();
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// This should be unreachable (one of the 3 above actions should always be possible).
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// Compare the real obtained depgraph against the simulation.
|
||||
|
||||
@@ -1012,6 +1012,21 @@ FUZZ_TARGET(txgraph)
|
||||
}
|
||||
assert(!top_sim.IsOversized());
|
||||
break;
|
||||
} else if (command-- == 0) {
|
||||
// GetMainMemoryUsage().
|
||||
auto usage = real->GetMainMemoryUsage();
|
||||
// Test stability.
|
||||
if (alt) {
|
||||
auto usage2 = real->GetMainMemoryUsage();
|
||||
assert(usage == usage2);
|
||||
}
|
||||
// Only empty graphs have 0 memory usage.
|
||||
if (main_sim.GetTransactionCount() == 0) {
|
||||
assert(usage == 0);
|
||||
} else {
|
||||
assert(usage > 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+753
-210
File diff suppressed because it is too large.
Load diff
@@ -202,6 +202,13 @@ public:
|
||||
* graph must not be oversized. If the graph is empty, {{}, FeePerWeight{}} is returned. */
|
||||
virtual std::pair<std::vector<Ref*>, FeePerWeight> GetWorstMainChunk() noexcept = 0;
|
||||
|
||||
/** Get the approximate memory usage for this object, just counting the main graph. If a
|
||||
* staging graph is present, return a number corresponding to memory usage after
|
||||
* AbortStaging() would be called. BlockBuilders' memory usage, memory usage of internally
|
||||
* queued operations, and memory due to temporary caches, is not included here. Can always be
|
||||
* called. */
|
||||
virtual size_t GetMainMemoryUsage() noexcept = 0;
|
||||
|
||||
/** Perform an internal consistency check on this object. */
|
||||
virtual void SanityCheck() const = 0;
|
||||
|
||||
|
||||
Reference in new issue
Block a user