Helios Engine 0.1.0
A modular ECS based data-oriented C++23 game engine
 
Loading...
Searching...
No Matches
helios::memory::PoolAllocator Class Referencefinal

Pool allocator for fixed-size allocations. More...

#include <pool_allocator.hpp>

Public Member Functions

 PoolAllocator (size_t block_size, size_t block_count, size_t alignment=kDefaultAlignment)
 Constructs a pool allocator.
 
 PoolAllocator (const PoolAllocator &)=delete
 
 PoolAllocator (PoolAllocator &&other) noexcept
 
 ~PoolAllocator () noexcept
 
PoolAllocatoroperator= (const PoolAllocator &)=delete
 
PoolAllocatoroperator= (PoolAllocator &&other) noexcept
 
AllocationResult Allocate (size_t size) noexcept
 Allocates a block from the pool.
 
template<typename T >
T * Allocate () noexcept
 
template<typename T , typename... Args>
requires std::constructible_from<T, Args...>
T * AllocateAndConstruct (Args &&... args) noexcept(std::is_nothrow_constructible_v< T, Args... >)
 
void Deallocate (void *ptr) noexcept
 Deallocates a block back to the pool.
 
void Reset () noexcept
 Resets the pool, making all blocks available.
 
bool Full () const noexcept
 Checks if the allocator is full.
 
bool Empty () const noexcept
 
bool Owns (const void *ptr) const noexcept
 Checks if a pointer belongs to this pool.
 
AllocatorStats Stats () const noexcept
 Gets current allocator statistics.
 
size_t BlockSize () const noexcept
 Gets the block size.
 
size_t BlockCount () const noexcept
 Gets the block count.
 
size_t Capacity () const noexcept
 Gets the total capacity of the pool.
 
size_t FreeBlockCount () const noexcept
 Gets the number of free blocks.
 
size_t UsedBlockCount () const noexcept
 Gets the number of used blocks.
 

Static Public Member Functions

template<typename T >
static PoolAllocator ForType (size_t block_count)
 

Detailed Description

Pool allocator for fixed-size allocations.

Allocates objects of a fixed size from a pre-allocated pool. Extremely efficient for scenarios where many objects of the same size are allocated and deallocated frequently.

Uses a lock-free free list to track available slots. Each free slot stores a pointer to the next free slot, forming a linked list through the free blocks.

Supports proper deallocation and reuse of freed blocks.

Uses lock-free atomic operations for allocation/deallocation, providing excellent performance in multi-threaded scenarios.

Note
Thread-safe with lock-free operations. All allocations must be the same size (or smaller than block_size).

Definition at line 36 of file pool_allocator.hpp.

Constructor & Destructor Documentation

◆ PoolAllocator() [1/3]

helios::memory::PoolAllocator::PoolAllocator ( size_t  block_size,
size_t  block_count,
size_t  alignment = kDefaultAlignment 
)
inline

Constructs a pool allocator.

Warning
Triggers assertion in next cases:
  • Block count is 0.
  • Alignment is not a power of 2.
  • Alignment is less than alignof(void*).
Parameters
block_sizeSize of each block in bytes (minimum 8 bytes for free list pointer)
block_countNumber of blocks to allocate
alignmentAlignment for each block (must be power of 2)
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/pool_allocator.hpp.

Definition at line 210 of file pool_allocator.hpp.

211 : block_size_(std::max(block_size, sizeof(void*))), // Minimum size for free list pointer
212 block_count_(block_count),
213 alignment_(alignment),
214 free_block_count_{block_count} {
215 HELIOS_ASSERT(block_count_ > 0, "Failed to construct PoolAllocator: block_count must be greater than 0!, got '{}'",
216 block_count_);
217 HELIOS_ASSERT(IsPowerOfTwo(alignment), "Failed to construct PoolAllocator: alignment must be power of 2, got '{}'!",
218 alignment);
219 HELIOS_ASSERT(alignment >= alignof(void*),
220 "Failed to construct PoolAllocator: alignment must be at least '{}', got '{}'!", alignof(void*),
221 alignment);
222
223 // Align block size to alignment
224 block_size_ = AlignUp(block_size_, alignment_);
225 capacity_ = block_size_ * block_count_;
226
227 // Allocate aligned buffer
228 buffer_ = AlignedAlloc(alignment_, capacity_);
229 HELIOS_VERIFY(buffer_ != nullptr, "Failed to construct PoolAllocator: Allocation of buffer failed!");
230
231 InitializeFreeList();
232 free_list_head_atomic_.store(free_list_head_, std::memory_order_release);
233}
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:140
#define HELIOS_VERIFY(condition,...)
Verify macro that always checks the condition.
Definition assert.hpp:196
void * AlignedAlloc(size_t alignment, size_t size)
Allocate memory with the specified alignment.
Definition common.hpp:30
constexpr size_t AlignUp(size_t size, size_t alignment) noexcept
Helper function to align a size up to the given alignment.
constexpr bool IsPowerOfTwo(size_t size) noexcept
Helper function to check if a size is a power of 2.

◆ PoolAllocator() [2/3]

helios::memory::PoolAllocator::PoolAllocator ( const PoolAllocator )
delete

◆ PoolAllocator() [3/3]

helios::memory::PoolAllocator::PoolAllocator ( PoolAllocator &&  other)
inlinenoexcept

Definition at line 235 of file pool_allocator.hpp.

236 : buffer_(other.buffer_),
237 free_list_head_(other.free_list_head_),
238 block_size_(other.block_size_),
239 block_count_(other.block_count_),
240 capacity_(other.capacity_),
241 alignment_(other.alignment_),
242 free_list_head_atomic_{other.free_list_head_atomic_.load(std::memory_order_acquire)},
243 free_block_count_{other.free_block_count_.load(std::memory_order_acquire)},
244 peak_used_blocks_{other.peak_used_blocks_.load(std::memory_order_acquire)},
245 total_allocations_{other.total_allocations_.load(std::memory_order_acquire)},
246 total_deallocations_{other.total_deallocations_.load(std::memory_order_acquire)} {
247 other.buffer_ = nullptr;
248 other.free_list_head_ = nullptr;
249 other.block_size_ = 0;
250 other.block_count_ = 0;
251 other.capacity_ = 0;
252 other.alignment_ = 0;
253 other.free_list_head_atomic_.store(nullptr, std::memory_order_release);
254 other.free_block_count_.store(0, std::memory_order_release);
255 other.peak_used_blocks_.store(0, std::memory_order_release);
256 other.total_allocations_.store(0, std::memory_order_release);
257 other.total_deallocations_.store(0, std::memory_order_release);
258}

◆ ~PoolAllocator()

helios::memory::PoolAllocator::~PoolAllocator ( )
inlinenoexcept
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/pool_allocator.hpp.

Definition at line 260 of file pool_allocator.hpp.

260 {
261 if (buffer_ != nullptr) {
262 AlignedFree(buffer_);
263 }
264}
void AlignedFree(void *ptr)
Free memory allocated with AlignedAlloc.
Definition common.hpp:53

Member Function Documentation

◆ Allocate() [1/2]

template<typename T >
T * helios::memory::PoolAllocator::Allocate ( )
inlinenoexcept
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/pool_allocator.hpp.

Definition at line 428 of file pool_allocator.hpp.

428 {
429 auto result = Allocate(sizeof(T));
430 return static_cast<T*>(result.ptr);
431}

◆ Allocate() [2/2]

AllocationResult helios::memory::PoolAllocator::Allocate ( size_t  size)
inlinenoexcept

Allocates a block from the pool.

Note
Size is ignored but kept for interface compatibility.
Warning
Triggers assertion if size exceeds block_size.
Parameters
sizeNumber of bytes to allocate (must be <= block_size)
Returns
AllocationResult with pointer and actual allocated size, or {nullptr, 0} on failure

Definition at line 305 of file pool_allocator.hpp.

305 {
306 if (size == 0) [[unlikely]] {
307 return {.ptr = nullptr, .allocated_size = 0};
308 }
309
310 HELIOS_ASSERT(size <= block_size_, "Failed to allocate memory: size '{}' exceeds block size '{}'!", size,
311 block_size_);
312
313 // Lock-free allocation using compare-and-swap
314 void* old_head = free_list_head_atomic_.load(std::memory_order_acquire);
315 void* new_head = nullptr;
316
317 do {
318 // Check if we have free blocks
319 if (old_head == nullptr) {
320 return {.ptr = nullptr, .allocated_size = 0};
321 }
322
323 // Read the next pointer from the old head
324 new_head = *reinterpret_cast<void**>(old_head);
325
326 // Try to atomically update the head pointer
327 } while (!free_list_head_atomic_.compare_exchange_weak(old_head, new_head, std::memory_order_release,
328 std::memory_order_acquire));
329
330 // Successfully allocated - update counters
331 free_block_count_.fetch_sub(1, std::memory_order_relaxed);
332 total_allocations_.fetch_add(1, std::memory_order_relaxed);
333
334 // Update peak usage
335 const size_t used_blocks = block_count_ - free_block_count_.load(std::memory_order_relaxed);
336 size_t current_peak = peak_used_blocks_.load(std::memory_order_acquire);
337 while (used_blocks > current_peak) {
338 if (peak_used_blocks_.compare_exchange_weak(current_peak, used_blocks, std::memory_order_release,
339 std::memory_order_acquire)) {
340 break;
341 }
342 }
343
344 return {old_head, block_size_};
345}

◆ AllocateAndConstruct()

template<typename T , typename... Args>
requires std::constructible_from<T, Args...>
T * helios::memory::PoolAllocator::AllocateAndConstruct ( Args &&...  args)
inlinenoexcept
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/pool_allocator.hpp.

Definition at line 435 of file pool_allocator.hpp.

435 {
436 T* ptr = Allocate<T>();
437 if (ptr != nullptr) [[likely]] {
438 std::construct_at(ptr, std::forward<Args>(args)...);
439 }
440 return ptr;
441}

◆ BlockCount()

size_t helios::memory::PoolAllocator::BlockCount ( ) const
inlinenoexcept

◆ BlockSize()

size_t helios::memory::PoolAllocator::BlockSize ( ) const
inlinenoexcept

◆ Capacity()

size_t helios::memory::PoolAllocator::Capacity ( ) const
inlinenoexcept

Gets the total capacity of the pool.

Returns
Capacity in bytes
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/pool_allocator.hpp.

Definition at line 175 of file pool_allocator.hpp.

175{ return capacity_; }

◆ Deallocate()

void helios::memory::PoolAllocator::Deallocate ( void *  ptr)
inlinenoexcept

Deallocates a block back to the pool.

Note
Returns the block to the free list for reuse.
Warning
Triggers assertion if pointer does not belong to this pool.
Parameters
ptrPointer to deallocate (must have been allocated from this pool)
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/pool_allocator.hpp.

Definition at line 347 of file pool_allocator.hpp.

347 {
348 if (ptr == nullptr) [[unlikely]] {
349 return;
350 }
351
352 HELIOS_ASSERT(Owns(ptr), "Failed to deallocate memory: ptr does not belong to this pool!");
353
354 // Lock-free deallocation using compare-and-swap
355 void* old_head = free_list_head_atomic_.load(std::memory_order_acquire);
356
357 do {
358 // Set the next pointer of the block to the current head
359 *reinterpret_cast<void**>(ptr) = old_head;
360
361 // Try to atomically update the head pointer
362 } while (!free_list_head_atomic_.compare_exchange_weak(old_head, ptr, std::memory_order_release,
363 std::memory_order_acquire));
364
365 // Successfully deallocated - update counters
366 free_block_count_.fetch_add(1, std::memory_order_relaxed);
367 total_deallocations_.fetch_add(1, std::memory_order_relaxed);
368}
bool Owns(const void *ptr) const noexcept
Checks if a pointer belongs to this pool.

◆ Empty()

bool helios::memory::PoolAllocator::Empty ( ) const
inlinenoexcept

◆ ForType()

template<typename T >
static PoolAllocator helios::memory::PoolAllocator::ForType ( size_t  block_count)
inlinestatic
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/pool_allocator.hpp.

Definition at line 50 of file pool_allocator.hpp.

50 {
51 constexpr size_t min_alignment = alignof(void*);
52 constexpr size_t type_alignment = alignof(T);
53 constexpr size_t alignment = type_alignment > min_alignment ? type_alignment : min_alignment;
54 return {sizeof(T), block_count, alignment};
55 }

◆ FreeBlockCount()

size_t helios::memory::PoolAllocator::FreeBlockCount ( ) const
inlinenoexcept

Gets the number of free blocks.

Returns
Number of blocks available for allocation
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/pool_allocator.hpp.

Definition at line 181 of file pool_allocator.hpp.

181{ return free_block_count_.load(std::memory_order_relaxed); }

◆ Full()

bool helios::memory::PoolAllocator::Full ( ) const
inlinenoexcept

Checks if the allocator is full.

Returns
True if all blocks are allocated
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/allocator_resources.hpp, and /home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/pool_allocator.hpp.

Definition at line 141 of file pool_allocator.hpp.

141{ return free_block_count_.load(std::memory_order_relaxed) == 0; }

◆ operator=() [1/2]

◆ operator=() [2/2]

PoolAllocator & helios::memory::PoolAllocator::operator= ( PoolAllocator &&  other)
inlinenoexcept

Definition at line 266 of file pool_allocator.hpp.

266 {
267 if (this == &other) [[unlikely]] {
268 return *this;
269 }
270
271 // Free current buffer
272 if (buffer_ != nullptr) {
273 AlignedFree(buffer_);
274 }
275
276 // Move from other
277 buffer_ = other.buffer_;
278 free_list_head_ = other.free_list_head_;
279 block_size_ = other.block_size_;
280 block_count_ = other.block_count_;
281 capacity_ = other.capacity_;
282 alignment_ = other.alignment_;
283 free_list_head_atomic_.store(other.free_list_head_atomic_.load(std::memory_order_acquire), std::memory_order_release);
284 free_block_count_.store(other.free_block_count_.load(std::memory_order_acquire), std::memory_order_release);
285 peak_used_blocks_.store(other.peak_used_blocks_.load(std::memory_order_acquire), std::memory_order_release);
286 total_allocations_.store(other.total_allocations_.load(std::memory_order_acquire), std::memory_order_release);
287 total_deallocations_.store(other.total_deallocations_.load(std::memory_order_acquire), std::memory_order_release);
288
289 // Reset other
290 other.buffer_ = nullptr;
291 other.free_list_head_ = nullptr;
292 other.block_size_ = 0;
293 other.block_count_ = 0;
294 other.capacity_ = 0;
295 other.alignment_ = 0;
296 other.free_list_head_atomic_.store(nullptr, std::memory_order_release);
297 other.free_block_count_.store(0, std::memory_order_release);
298 other.peak_used_blocks_.store(0, std::memory_order_release);
299 other.total_allocations_.store(0, std::memory_order_release);
300 other.total_deallocations_.store(0, std::memory_order_release);
301
302 return *this;
303}

◆ Owns()

bool helios::memory::PoolAllocator::Owns ( const void *  ptr) const
inlinenoexcept

Checks if a pointer belongs to this pool.

Parameters
ptrPointer to check
Returns
True if pointer is within pool's memory range
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/pool_allocator.hpp.

Definition at line 377 of file pool_allocator.hpp.

377 {
378 if (ptr == nullptr || buffer_ == nullptr) {
379 return false;
380 }
381
382 const auto addr = reinterpret_cast<uintptr_t>(ptr);
383 const auto start = reinterpret_cast<uintptr_t>(buffer_);
384 const auto end = start + capacity_;
385
386 return addr >= start && addr < end;
387}

◆ Reset()

void helios::memory::PoolAllocator::Reset ( )
inlinenoexcept

Resets the pool, making all blocks available.

Rebuilds the free list, invalidating all current allocations.

Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/allocator_resources.hpp, and /home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/pool_allocator.hpp.

Definition at line 370 of file pool_allocator.hpp.

370 {
371 InitializeFreeList();
372 free_list_head_atomic_.store(free_list_head_, std::memory_order_release);
373 free_block_count_.store(block_count_, std::memory_order_release);
374 // Don't reset peak stats
375}

◆ Stats()

AllocatorStats helios::memory::PoolAllocator::Stats ( ) const
inlinenoexcept

Gets current allocator statistics.

Returns
AllocatorStats with current usage information
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/allocator_resources.hpp, and /home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/pool_allocator.hpp.

Definition at line 389 of file pool_allocator.hpp.

389 {
390 const size_t free_blocks = free_block_count_.load(std::memory_order_relaxed);
391 const size_t used_blocks = block_count_ - free_blocks;
392 const size_t used_bytes = used_blocks * block_size_;
393 const size_t peak_blocks = peak_used_blocks_.load(std::memory_order_relaxed);
394 const size_t peak_bytes = peak_blocks * block_size_;
395 const size_t total_allocs = total_allocations_.load(std::memory_order_relaxed);
396 const size_t total_deallocs = total_deallocations_.load(std::memory_order_relaxed);
397
398 return {
399 .total_allocated = used_bytes,
400 .total_freed = 0,
401 .peak_usage = peak_bytes,
402 .allocation_count = used_blocks,
403 .total_allocations = total_allocs,
404 .total_deallocations = total_deallocs,
405 .alignment_waste = 0,
406 };
407}

◆ UsedBlockCount()

size_t helios::memory::PoolAllocator::UsedBlockCount ( ) const
inlinenoexcept

Gets the number of used blocks.

Returns
Number of blocks currently allocated
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/pool_allocator.hpp.

Definition at line 187 of file pool_allocator.hpp.

187 {
188 return block_count_ - free_block_count_.load(std::memory_order_relaxed);
189 }