This commit is contained in:
Larry Ruane 2025-04-29 12:07:27 +02:00 committed by GitHub
commit 025b0784fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 41 additions and 22 deletions

View file

@ -24,9 +24,6 @@
namespace memusage
{
/** Compute the total memory used by allocating alloc bytes. */
static size_t MallocUsage(size_t alloc);
/** Dynamic memory usage for built-in types is zero. */
static inline size_t DynamicUsage(const int8_t& v) { return 0; }
static inline size_t DynamicUsage(const uint8_t& v) { return 0; }
@ -48,19 +45,21 @@ template<typename X> static inline size_t DynamicUsage(const X * const &v) { ret
* application data structures require more accurate inner accounting, they should
* iterate themselves, or use more efficient caching + updating on modification.
*/
static inline size_t MallocUsage(size_t alloc)
static constexpr size_t MallocUsage(size_t alloc)
{
// Measured on libc6 2.19 on Linux.
if (alloc == 0) {
return 0;
} else if (sizeof(void*) == 8) {
return ((alloc + 31) >> 4) << 4;
} else if (sizeof(void*) == 4) {
return ((alloc + 15) >> 3) << 3;
} else {
assert(0);
}
if (alloc == 0) return 0;
#if defined(__arm__) || SIZE_MAX == UINT64_MAX
constexpr size_t min_alloc{9};
#else
constexpr size_t min_alloc{0};
#endif
constexpr size_t overhead{sizeof(size_t)};
constexpr size_t step{alignof(std::max_align_t)};
// step should be a nonzero power of 2 (exactly one bit set)
static_assert(step > 0 && (step & (step - 1)) == 0);
return (std::max(min_alloc, alloc) + overhead + (step - 1)) & ~(step - 1);
}
// STL data structures
@ -177,23 +176,33 @@ static inline size_t DynamicUsage(const std::list<X>& l)
return MallocUsage(sizeof(list_node<X>)) * l.size();
}
// Empirically, an std::unordered_map node has two pointers (likely
// forward and backward pointers) on some platforms (Windows and macOS),
// so be conservative in estimating memory usage by assuming this is
// the case for all platforms.
template<typename X>
struct unordered_node : private X
{
private:
void* ptr;
void* ptr2;
};
// The memory used by an unordered_set or unordered map is the sum of the
// sizes of the individual nodes (which are separately allocated) plus
// the size of the bucket array (which is a single allocation).
// Empirically, each element of the bucket array consists of two pointers
// on some platforms (Windows and macOS), so be conservative.
template<typename X, typename Y>
static inline size_t DynamicUsage(const std::unordered_set<X, Y>& s)
{
return MallocUsage(sizeof(unordered_node<X>)) * s.size() + MallocUsage(sizeof(void*) * s.bucket_count());
return MallocUsage(sizeof(unordered_node<X>)) * s.size() + MallocUsage(2 * sizeof(void*) * s.bucket_count());
}
template<typename X, typename Y, typename Z>
static inline size_t DynamicUsage(const std::unordered_map<X, Y, Z>& m)
{
return MallocUsage(sizeof(unordered_node<std::pair<const X, Y> >)) * m.size() + MallocUsage(sizeof(void*) * m.bucket_count());
return MallocUsage(sizeof(unordered_node<std::pair<const X, Y> >)) * m.size() + MallocUsage(2 * sizeof(void*) * m.bucket_count());
}
template <class Key, class T, class Hash, class Pred, std::size_t MAX_BLOCK_SIZE_BYTES, std::size_t ALIGN_BYTES>
@ -212,7 +221,7 @@ static inline size_t DynamicUsage(const std::unordered_map<Key,
size_t estimated_list_node_size = MallocUsage(sizeof(void*) * 3);
size_t usage_resource = estimated_list_node_size * pool_resource->NumAllocatedChunks();
size_t usage_chunks = MallocUsage(pool_resource->ChunkSizeBytes()) * pool_resource->NumAllocatedChunks();
return usage_resource + usage_chunks + MallocUsage(sizeof(void*) * m.bucket_count());
return usage_resource + usage_chunks + MallocUsage(2 * sizeof(void*) * m.bucket_count());
}
} // namespace memusage

View file

@ -30,7 +30,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate)
// (prevector<28, unsigned char>) when assigned 56 bytes of data per above.
//
// See also: Coin::DynamicMemoryUsage().
constexpr unsigned int COIN_SIZE = is_64_bit ? 80 : 64;
constexpr size_t COIN_SIZE{64};
auto print_view_mem_usage = [](CCoinsViewCache& view) {
BOOST_TEST_MESSAGE("CCoinsViewCache memory usage: " << view.DynamicMemoryUsage());

View file

@ -57,16 +57,17 @@ def fill_mempool(test_framework, node, *, tx_sync_fun=None):
# Generate UTXOs to flood the mempool
# 1 to create a tx initially that will be evicted from the mempool later
# 75 transactions each with a fee rate higher than the previous one
# 1 to create a final middle-sized tx to evict a filler tx and create room (if needed)
ephemeral_miniwallet = MiniWallet(node, tag_name="fill_mempool_ephemeral_wallet")
test_framework.generate(ephemeral_miniwallet, 1 + num_of_batches * tx_batch_size)
test_framework.generate(ephemeral_miniwallet, 2 + num_of_batches * tx_batch_size)
# Mine enough blocks so that the UTXOs are allowed to be spent
test_framework.generate(node, COINBASE_MATURITY - 1)
# Get all UTXOs up front to ensure none of the transactions spend from each other, as that may
# change their effective feerate and thus the order in which they are selected for eviction.
confirmed_utxos = [ephemeral_miniwallet.get_utxo(confirmed_only=True) for _ in range(num_of_batches * tx_batch_size + 1)]
assert_equal(len(confirmed_utxos), num_of_batches * tx_batch_size + 1)
confirmed_utxos = [ephemeral_miniwallet.get_utxo(confirmed_only=True) for _ in range(num_of_batches * tx_batch_size + 2)]
assert_equal(len(confirmed_utxos), num_of_batches * tx_batch_size + 2)
test_framework.log.debug("Create a mempool tx that will be evicted")
tx_to_be_evicted_id = ephemeral_miniwallet.send_self_transfer(
@ -92,6 +93,14 @@ def fill_mempool(test_framework, node, *, tx_sync_fun=None):
send_batch(fee)
tx_sync_fun() if tx_sync_fun else test_framework.sync_mempools() # sync after all evictions
# If the mempool is almost full (<10k usage bytes left), submit one extra middle-sized tx,
# in order to evict a large filler tx and leave some room; this will enable tests to submit
# txs with just the mempoolminfee without immediate eviction ("mempool full" error).
if node.getmempoolinfo()['usage'] >= 4_990_000:
ephemeral_miniwallet.send_self_transfer(from_node=node, fee=num_of_batches * (base_fee / 2),
utxo_to_spend=confirmed_utxos.pop(0), target_vsize = 32500)
assert_greater_than(4_990_000, node.getmempoolinfo()['usage'])
test_framework.log.debug("The tx should be evicted by now")
# The number of transactions created should be greater than the ones present in the mempool
assert_greater_than(tx_batch_size * num_of_batches, len(node.getrawmempool()))
@ -101,6 +110,7 @@ def fill_mempool(test_framework, node, *, tx_sync_fun=None):
test_framework.log.debug("Check that mempoolminfee is larger than minrelaytxfee")
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
assert_greater_than(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))
test_framework.sync_mempools()
def tx_in_orphanage(node, tx: CTransaction) -> bool:
"""Returns true if the transaction is in the orphanage."""