- statistics for pool allocator

This commit is contained in:
2026-07-22 17:28:13 +03:00
parent a19697f501
commit d22b8fe1ab
+54
View File
@@ -11,6 +11,7 @@
/// transparent passthrough to the global allocator (i.e. allocate_shared behaves like make_shared) /// transparent passthrough to the global allocator (i.e. allocate_shared behaves like make_shared)
/// for A/B benchmarking without touching the call sites. /// for A/B benchmarking without touching the call sites.
#include <atomic>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <mutex> #include <mutex>
@@ -45,6 +46,7 @@ namespace hl
{ {
uint8_t* raw = static_cast<uint8_t*>(::operator new(size + HeaderSize)); uint8_t* raw = static_cast<uint8_t*>(::operator new(size + HeaderSize));
tagOf(raw) = TagGlobal; tagOf(raw) = TagGlobal;
s_activeGlobal.fetch_add(1, std::memory_order_relaxed);
return raw + HeaderSize; return raw + HeaderSize;
} }
@@ -54,6 +56,7 @@ namespace hl
uint8_t* block = static_cast<uint8_t*>(head); uint8_t* block = static_cast<uint8_t*>(head);
head = nextOf(block); head = nextOf(block);
s_activeBlocks.fetch_add(1, std::memory_order_relaxed);
return block + HeaderSize; return block + HeaderSize;
} }
@@ -68,14 +71,49 @@ namespace hl
void*& head = freeListHead(); void*& head = freeListHead();
nextOf(block) = head; nextOf(block) = head;
head = block; head = block;
s_activeBlocks.fetch_sub(1, std::memory_order_relaxed);
} }
else else
{ {
::operator delete(static_cast<void*>(block)); ::operator delete(static_cast<void*>(block));
s_activeGlobal.fetch_sub(1, std::memory_order_relaxed);
} }
} }
/// @name Diagnostics (relaxed gauges — not synchronization)
/// @{
/// Blocks currently handed out from the pool = pooled allocate() minus
/// pooled deallocate(). Unlike capacityBlocks() (the chunk high-water,
/// which only ever grows) this DROPS when objects are freed, so a rising
/// activeBlocks() is a genuine leak of referenced objects rather than a
/// traffic peak that merely carved extra chunks. Covers every pooled
/// shared_ptr node (allocate_shared<RTPPacket> + jitter-buffer packets).
static std::int64_t activeBlocks() noexcept
{
return s_activeBlocks.load(std::memory_order_relaxed);
}
/// Live oversized allocations that overflowed to ::operator new.
static std::int64_t activeGlobalAllocations() noexcept
{
return s_activeGlobal.load(std::memory_order_relaxed);
}
/// Total blocks ever carved (chunks x BlocksPerChunk) = high-water capacity.
static std::int64_t capacityBlocks() noexcept
{
return s_chunks.load(std::memory_order_relaxed) * static_cast<std::int64_t>(BlocksPerChunk);
}
/// Approximate live pooled payload bytes (excludes per-block header).
static std::int64_t activeBytes() noexcept
{
return activeBlocks() * static_cast<std::int64_t>(PayloadSize);
}
/// @}
private: private:
inline static std::atomic<std::int64_t> s_activeBlocks{0};
inline static std::atomic<std::int64_t> s_activeGlobal{0};
inline static std::atomic<std::int64_t> s_chunks{0};
static constexpr std::size_t HeaderSize = static constexpr std::size_t HeaderSize =
alignof(std::max_align_t) >= sizeof(uint64_t) ? alignof(std::max_align_t) : sizeof(uint64_t); alignof(std::max_align_t) >= sizeof(uint64_t) ? alignof(std::max_align_t) : sizeof(uint64_t);
static constexpr std::size_t BlockSize = HeaderSize + PayloadSize; static constexpr std::size_t BlockSize = HeaderSize + PayloadSize;
@@ -114,6 +152,7 @@ namespace hl
{ {
const std::size_t chunkBytes = BlockSize * BlocksPerChunk; const std::size_t chunkBytes = BlockSize * BlocksPerChunk;
uint8_t* chunk = static_cast<uint8_t*>(::operator new(chunkBytes)); uint8_t* chunk = static_cast<uint8_t*>(::operator new(chunkBytes));
s_chunks.fetch_add(1, std::memory_order_relaxed);
{ {
std::lock_guard<std::mutex> lock(m_Mutex); std::lock_guard<std::mutex> lock(m_Mutex);
@@ -144,6 +183,21 @@ namespace hl
}; };
#endif // HL_RTP_POOL #endif // HL_RTP_POOL
/// Mode-independent accessors for the pool diagnostics, so callers need not know
/// whether HL_RTP_POOL is compiled in. When pooling is disabled they return -1
/// ("not applicable") since allocate_shared then behaves like make_shared.
#if HL_RTP_POOL
inline std::int64_t poolActiveBlocks() noexcept { return FixedBlockPool::activeBlocks(); }
inline std::int64_t poolCapacityBlocks() noexcept { return FixedBlockPool::capacityBlocks(); }
inline std::int64_t poolActiveGlobal() noexcept { return FixedBlockPool::activeGlobalAllocations(); }
inline std::int64_t poolActiveBytes() noexcept { return FixedBlockPool::activeBytes(); }
#else
inline std::int64_t poolActiveBlocks() noexcept { return -1; }
inline std::int64_t poolCapacityBlocks() noexcept { return -1; }
inline std::int64_t poolActiveGlobal() noexcept { return -1; }
inline std::int64_t poolActiveBytes() noexcept { return -1; }
#endif
/// @class PoolAllocator /// @class PoolAllocator
/// A stateless, std-conforming Allocator suitable for std::allocate_shared. When HL_RTP_POOL is /// A stateless, std-conforming Allocator suitable for std::allocate_shared. When HL_RTP_POOL is
/// enabled it serves single-node allocations from FixedBlockPool; otherwise (and for any request /// enabled it serves single-node allocations from FixedBlockPool; otherwise (and for any request