Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
fixed_pool_allocator.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/assert.hpp>
6#include <helios/memory/details/profile.hpp>
7
8#include <algorithm>
9#include <atomic>
10#include <cstddef>
11#include <memory_resource>
12
13namespace helios::mem {
14
15/// @brief Configuration for FixedPoolAllocator.
17 /// @brief Minimum payload size of each pool block in bytes.
18 size_t block_size = 0;
19 /// @brief Number of blocks in the pool.
20 size_t block_count = 0;
21 /// @brief Alignment of each block in bytes; must be a power of two.
23};
24
25/**
26 * @brief Fixed-capacity intrusive block pool.
27 * @details Lock-free Treiber free-list over a single preallocated chunk.
28 * @warning `Reset()` must not run concurrently with `allocate` or `deallocate`.
29 */
30class FixedPoolAllocator final : public std::pmr::memory_resource {
31public:
32 /**
33 * @brief Constructs fixed pool from options.
34 * @warning Triggers assertion when block_size or block_count is zero, or when
35 * alignment is invalid. Triggers verification failure if backing allocation
36 * fails.
37 * @param options Fixed pool configuration
38 */
39 explicit FixedPoolAllocator(FixedPoolAllocatorOptions options) noexcept;
40
41 /**
42 * @brief Constructs fixed pool with explicit block geometry.
43 * @warning Triggers assertion when `block_size` or `block_count` is zero, or
44 * when alignment is invalid.
45 * @param block_size Minimum payload size of each block in bytes
46 * @param block_count Number of blocks in the pool
47 * @param alignment Block alignment in bytes
48 */
49 FixedPoolAllocator(size_t block_size, size_t block_count,
50 size_t alignment = kDefaultAlignment) noexcept;
52
53 /**
54 * @brief Move-constructs fixed pool from another instance.
55 * @param other Source allocator; left in empty moved-from state
56 */
57 FixedPoolAllocator(FixedPoolAllocator&& other) noexcept { MoveFrom(other); }
58 ~FixedPoolAllocator() noexcept override { Release(); }
59
61
62 /**
63 * @brief Move-assigns fixed pool state from another instance.
64 * @param other Source allocator; left in empty moved-from state
65 * @return Reference to this allocator
66 */
68
69 /**
70 * @brief Helper for constructing fixed pool allocator for a specific type.
71 * @tparam T Type to pool
72 * @param block_count Number of blocks in the pool
73 * @return Configured fixed pool allocator
74 */
75 template <typename T>
76 [[nodiscard]] static FixedPoolAllocator ForType(size_t block_count) noexcept;
77
78 /**
79 * @brief Resets pool to fully free state.
80 * @warning Must not be called concurrently with `allocate` or `deallocate`.
81 */
82 void Reset() noexcept;
83
84 /**
85 * @brief Returns true when no free blocks remain.
86 * @return True if full, false otherwise
87 */
88 [[nodiscard]] bool Full() const noexcept {
89 return free_blocks_.load(std::memory_order_relaxed) == 0;
90 }
91
92 /**
93 * @brief Returns true when all blocks are free.
94 * @return True if empty, false otherwise
95 */
96 [[nodiscard]] bool Empty() const noexcept {
97 return free_blocks_.load(std::memory_order_relaxed) == block_count_;
98 }
99
100 /**
101 * @brief Checks pointer ownership.
102 * @param ptr Pointer to test
103 * @return True when pointer belongs to this pool chunk, false otherwise
104 */
105 [[nodiscard]] bool Owns(const void* ptr) const noexcept;
106
107 /**
108 * @brief Returns runtime statistics snapshot.
109 * @return Allocator stats for this pool
110 */
111 [[nodiscard]] AllocatorStats Stats() const noexcept;
112
113 /**
114 * @brief Returns effective block size.
115 * @return Block size in bytes
116 */
117 [[nodiscard]] size_t BlockSize() const noexcept { return block_size_; }
118
119 /**
120 * @brief Returns configured block alignment.
121 * @return Alignment in bytes
122 */
123 [[nodiscard]] size_t Alignment() const noexcept { return alignment_; }
124
125 /**
126 * @brief Returns configured block count.
127 * @return Number of blocks in the pool
128 */
129 [[nodiscard]] size_t InitialBlockCount() const noexcept {
130 return block_count_;
131 }
132
133 /**
134 * @brief Returns configured block count.
135 * @return Number of blocks in the pool
136 */
137 [[nodiscard]] size_t BlockCount() const noexcept { return block_count_; }
138
139 /**
140 * @brief Returns free block count.
141 * @return Number of blocks currently on the free list
142 */
143 [[nodiscard]] size_t FreeBlockCount() const noexcept {
144 return free_blocks_.load(std::memory_order_relaxed);
145 }
146
147 /**
148 * @brief Returns used block count.
149 * @return Number of blocks currently allocated
150 */
151 [[nodiscard]] size_t UsedBlockCount() const noexcept {
152 return block_count_ - FreeBlockCount();
153 }
154
155private:
156 void MoveFrom(FixedPoolAllocator& other) noexcept;
157 void Rebuild() noexcept;
158 void Release() noexcept;
159
160 [[nodiscard]] void* do_allocate(size_t bytes, size_t alignment) override;
161 void do_deallocate(void* ptr, size_t bytes, size_t alignment) override;
162 [[nodiscard]] bool do_is_equal(
163 const std::pmr::memory_resource& other) const noexcept override {
164 return this == &other;
165 }
166
167 size_t block_size_ = 0;
168 size_t block_count_ = 0;
169 size_t alignment_ = 0;
170 size_t chunk_capacity_ = 0;
171 std::byte* buffer_ = nullptr;
172 std::atomic<void*> free_head_{nullptr};
173 std::atomic<size_t> free_blocks_{0};
174 std::atomic<size_t> peak_used_blocks_{0};
175 std::atomic<size_t> total_allocations_{0};
176 std::atomic<size_t> total_deallocations_{0};
177};
178
180 FixedPoolAllocatorOptions options) noexcept
181 : block_size_(std::max(options.block_size, sizeof(void*))),
182 block_count_(options.block_count),
183 alignment_(options.alignment) {
184 HELIOS_ASSERT(block_size_ > 0, "block_size must be greater than zero!");
185 HELIOS_ASSERT(block_count_ > 0, "block_count must be greater than zero!");
186 HELIOS_ASSERT(IsPowerOfTwo(alignment_),
187 "alignment '{}' must be power of two!", alignment_);
188 HELIOS_ASSERT(alignment_ >= alignof(void*), "alignment '{}' must be >= '{}'!",
189 alignment_, alignof(void*));
190
191 block_size_ = AlignUp(block_size_, alignment_);
192 chunk_capacity_ = block_size_ * block_count_;
193
194 buffer_ =
195 static_cast<std::byte*>(AlignedAlloc(alignment_, chunk_capacity_, false));
196 HELIOS_VERIFY(buffer_ != nullptr, "Failed to allocate fixed pool!");
197 HELIOS_MEMORY_PROFILE_ALLOC(buffer_, chunk_capacity_, "FixedPoolAllocator");
198 Rebuild();
199}
200
202 size_t block_count,
203 size_t alignment) noexcept
205 .block_size = block_size,
206 .block_count = block_count,
207 .alignment = alignment,
208 }) {}
209
211 FixedPoolAllocator&& other) noexcept {
212 if (this == &other) [[unlikely]] {
213 return *this;
214 }
215
216 Release();
217 MoveFrom(other);
218 return *this;
219}
220
221template <typename T>
223 size_t block_count) noexcept {
224 constexpr size_t type_align =
225 alignof(T) > alignof(void*) ? alignof(T) : alignof(void*);
226 return {sizeof(T), block_count, type_align};
227}
228
229inline void FixedPoolAllocator::Reset() noexcept {
230 HELIOS_MEMORY_PROFILE_SCOPE_N("helios::mem::FixedPoolAllocator::Reset");
231
232 Rebuild();
233 peak_used_blocks_.store(0, std::memory_order_relaxed);
234 total_allocations_.store(0, std::memory_order_relaxed);
235 total_deallocations_.store(0, std::memory_order_relaxed);
236}
237
238inline bool FixedPoolAllocator::Owns(const void* ptr) const noexcept {
239 HELIOS_MEMORY_PROFILE_SCOPE_N("helios::mem::FixedPoolAllocator::Owns");
240
241 if (buffer_ == nullptr || ptr == nullptr) [[unlikely]] {
242 return false;
243 }
244
245 const auto address = reinterpret_cast<uintptr_t>(ptr);
246 const auto begin = reinterpret_cast<uintptr_t>(buffer_);
247 return address >= begin && address < begin + chunk_capacity_ &&
248 (address - begin) % block_size_ == 0;
249}
250
252 const size_t used = UsedBlockCount();
253 return {
254 .total_allocated = used * block_size_,
255 .peak_usage =
256 peak_used_blocks_.load(std::memory_order_relaxed) * block_size_,
257 .allocation_count = used,
258 .total_allocations = total_allocations_.load(std::memory_order_relaxed),
259 .total_deallocations =
260 total_deallocations_.load(std::memory_order_relaxed),
261 .alignment_waste = 0,
262 };
263}
264
265} // namespace helios::mem
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:259
#define HELIOS_VERIFY(condition,...)
Verify macro that always checks the condition.
Definition assert.hpp:323
Fixed-capacity intrusive block pool.
FixedPoolAllocator & operator=(const FixedPoolAllocator &)=delete
size_t Alignment() const noexcept
Returns configured block alignment.
FixedPoolAllocator(FixedPoolAllocatorOptions options) noexcept
Constructs fixed pool from options.
FixedPoolAllocator(FixedPoolAllocator &&other) noexcept
Move-constructs fixed pool from another instance.
size_t UsedBlockCount() const noexcept
Returns used block count.
FixedPoolAllocator(const FixedPoolAllocator &)=delete
AllocatorStats Stats() const noexcept
Returns runtime statistics snapshot.
size_t BlockCount() const noexcept
Returns configured block count.
size_t FreeBlockCount() const noexcept
Returns free block count.
size_t InitialBlockCount() const noexcept
Returns configured block count.
static FixedPoolAllocator ForType(size_t block_count) noexcept
Helper for constructing fixed pool allocator for a specific type.
size_t BlockSize() const noexcept
Returns effective block size.
bool Empty() const noexcept
Returns true when all blocks are free.
void Reset() noexcept
Resets pool to fully free state.
bool Full() const noexcept
Returns true when no free blocks remain.
bool Owns(const void *ptr) const noexcept
Checks pointer ownership.
constexpr bool IsPowerOfTwo(size_t value) noexcept
Checks if a value is a power of two.
Definition common.hpp:215
constexpr size_t AlignUp(size_t value, size_t alignment) noexcept
Aligns value up to alignment.
Definition common.hpp:226
void * AlignedAlloc(size_t alignment, size_t size, bool enable_profile=true) noexcept
Allocates memory with the specified alignment.
constexpr size_t kDefaultAlignment
Default alignment used by memory resources.
Definition common.hpp:14
STL namespace.
Runtime statistics snapshot for PMR allocators.
Definition common.hpp:163
Configuration for FixedPoolAllocator.
size_t alignment
Alignment of each block in bytes; must be a power of two.
size_t block_count
Number of blocks in the pool.
size_t block_size
Minimum payload size of each pool block in bytes.