Skip to content

Commit

Permalink
util: generalize accounting of system-allocated memory in pool resource
Browse files Browse the repository at this point in the history
Follow-on to PR 25325, "Add pool based memory resource". No functional
change.

- Track non-internal system memory allocation (new and free) in m_system_alloc_bytes
- Move DynamicUsage() implementation details from memusage.h to pool.h
  • Loading branch information
LarryRuane committed Sep 21, 2023
1 parent b66f6dc commit 5d079ae
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 16 deletions.
9 changes: 1 addition & 8 deletions src/memusage.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,7 @@ static inline size_t DynamicUsage(const std::unordered_map<Key,
MAX_BLOCK_SIZE_BYTES,
ALIGN_BYTES>>& m)
{
auto* pool_resource = m.get_allocator().resource();

// The allocated chunks are stored in a std::list. Size per node should
// therefore be 3 pointers: next, previous, and a pointer to the chunk.
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 m.get_allocator().resource()->MemoryUsage();
}

} // namespace memusage
Expand Down
46 changes: 38 additions & 8 deletions src/support/allocators/pool.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,16 @@ class PoolResource final
*/
std::byte* m_available_memory_end = nullptr;

/**
* Return the number of bytes allocated by the system allocator,
* not including internal allocations for the resource itself.
*
* These are allocations that couldn't be satisfied by the resource
* allocator, because of either alignment or size restrictions
* (that is, IsFreeListUsable() returned false).
*/
size_t m_system_alloc_bytes{0};

/**
* How many multiple of ELEM_ALIGN_BYTES are necessary to fit bytes. We use that result directly as an index
* into m_free_lists. Round up for the special case when bytes==0.
Expand Down Expand Up @@ -164,6 +174,21 @@ class PoolResource final
m_allocated_chunks.emplace_back(m_available_memory_it);
}

// copied from src/memusage.h
size_t MallocUsage(size_t alloc) const
{
// 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);
}
}

/**
* Access to internals for testing purpose only
*/
Expand Down Expand Up @@ -232,6 +257,7 @@ class PoolResource final
}

// Can't use the pool => use operator new()
m_system_alloc_bytes += MallocUsage(bytes);
return ::operator new (bytes, std::align_val_t{alignment});
}

Expand All @@ -247,25 +273,29 @@ class PoolResource final
PlacementAddToList(p, m_free_lists[num_alignments]);
} else {
// Can't use the pool => forward deallocation to ::operator delete().
m_system_alloc_bytes -= MallocUsage(bytes);
::operator delete (p, std::align_val_t{alignment});
}
}

/**
* Number of allocated chunks
*/
[[nodiscard]] std::size_t NumAllocatedChunks() const
{
return m_allocated_chunks.size();
}

/**
* Size in bytes to allocate per chunk, currently hardcoded to a fixed size.
*/
[[nodiscard]] size_t ChunkSizeBytes() const
{
return m_chunk_size_bytes;
}

[[nodiscard]] size_t MemoryUsage() const
{
// The allocated chunks are stored in a std::list. Size per node should
// therefore be 3 pointers: next, previous, and a pointer to the chunk.
size_t estimated_list_node_size = MallocUsage(sizeof(void*) * 3);
size_t usage_resource = estimated_list_node_size * m_allocated_chunks.size();
size_t usage_chunks = MallocUsage(m_chunk_size_bytes) * m_allocated_chunks.size();
size_t usage_system = m_system_alloc_bytes;
return usage_resource + usage_chunks + usage_system;
}
};


Expand Down
7 changes: 7 additions & 0 deletions src/test/pool_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,33 +54,40 @@ BOOST_AUTO_TEST_CASE(basic_allocating)
PoolResourceTester::CheckAllDataAccountedFor(resource);
BOOST_TEST(1 == PoolResourceTester::FreeListSizes(resource)[1]);
BOOST_TEST(expected_bytes_available == PoolResourceTester::AvailableMemoryFromChunk(resource));
// the system allocates 32 or 16 bytes for an 8-byte allocation
BOOST_TEST(((sizeof(void*) == 8) ? 32 : 16) == PoolResourceTester::GetSystemAllocBytes(resource));

resource.Deallocate(block, 8, 16);
PoolResourceTester::CheckAllDataAccountedFor(resource);
BOOST_TEST(1 == PoolResourceTester::FreeListSizes(resource)[1]);
BOOST_TEST(expected_bytes_available == PoolResourceTester::AvailableMemoryFromChunk(resource));
BOOST_TEST(0 == PoolResourceTester::GetSystemAllocBytes(resource));

// can't use chunk because size is too big
block = resource.Allocate(16, 8);
PoolResourceTester::CheckAllDataAccountedFor(resource);
BOOST_TEST(1 == PoolResourceTester::FreeListSizes(resource)[1]);
BOOST_TEST(expected_bytes_available == PoolResourceTester::AvailableMemoryFromChunk(resource));
BOOST_TEST(((sizeof(void*) == 8) ? 32 : 16) == PoolResourceTester::GetSystemAllocBytes(resource));

resource.Deallocate(block, 16, 8);
PoolResourceTester::CheckAllDataAccountedFor(resource);
BOOST_TEST(1 == PoolResourceTester::FreeListSizes(resource)[1]);
BOOST_TEST(expected_bytes_available == PoolResourceTester::AvailableMemoryFromChunk(resource));
BOOST_TEST(0 == PoolResourceTester::GetSystemAllocBytes(resource));

// it's possible that 0 bytes are allocated, make sure this works. In that case the call is forwarded to operator new
// 0 bytes takes one entry from the first freelist
void* p = resource.Allocate(0, 1);
BOOST_TEST(0 == PoolResourceTester::FreeListSizes(resource)[1]);
BOOST_TEST(expected_bytes_available == PoolResourceTester::AvailableMemoryFromChunk(resource));
BOOST_TEST(0 == PoolResourceTester::GetSystemAllocBytes(resource));

resource.Deallocate(p, 0, 1);
PoolResourceTester::CheckAllDataAccountedFor(resource);
BOOST_TEST(1 == PoolResourceTester::FreeListSizes(resource)[1]);
BOOST_TEST(expected_bytes_available == PoolResourceTester::AvailableMemoryFromChunk(resource));
BOOST_TEST(0 == PoolResourceTester::GetSystemAllocBytes(resource));
}

// Allocates from 0 to n bytes were n > the PoolResource's data, and each should work
Expand Down
6 changes: 6 additions & 0 deletions src/test/util/poolresourcetester.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ class PoolResourceTester
assert(chunk_it == chunks.end());
assert(chunk_size_remaining == 0);
}

template <std::size_t MAX_BLOCK_SIZE_BYTES, std::size_t ALIGN_BYTES>
static size_t GetSystemAllocBytes(const PoolResource<MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES>& resource)
{
return resource.m_system_alloc_bytes;
}
};

#endif // BITCOIN_TEST_UTIL_POOLRESOURCETESTER_H

0 comments on commit 5d079ae

Please sign in to comment.