Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
paged_allocator.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cinttypes>
5#include <cstdint>
6#include <cstring>
7#include <type_traits>
8
9#include "gaia/cnt/fwd_llist.h"
10#include "gaia/cnt/sarray.h"
11#include "gaia/core/bit_utils.h"
12#include "gaia/core/dyn_singleton.h"
13#include "gaia/core/utility.h"
14#include "gaia/mem/mem_alloc.h"
15#include "gaia/meta/type_info.h"
16#include "gaia/util/logging.h"
17
18namespace gaia {
19 namespace mem {
20 static constexpr uint32_t MemoryBlockAlignment = 16;
23 static constexpr uint32_t MemoryBlockBytesDefault = 32768;
25 static constexpr uint32_t MemoryBlockUsableOffset = sizeof(uintptr_t);
26
27 struct GAIA_API MemoryPageHeader {
29 void* m_data;
30
31 MemoryPageHeader(void* ptr): m_data(ptr) {}
32 };
33
34 template <typename T, uint32_t RequestedBlockSize>
35 struct MemoryPage: MemoryPageHeader, cnt::fwd_llist_base<MemoryPage<T, RequestedBlockSize>> {
36 static constexpr uint32_t next_multiple_of_alignment(uint32_t num) {
37 return (num + (MemoryBlockAlignment - 1)) & uint32_t(-(int32_t)MemoryBlockAlignment);
38 }
39 static constexpr uint32_t calculate_block_size() {
40 if constexpr (RequestedBlockSize == 0)
41 return next_multiple_of_alignment(MemoryBlockBytesDefault);
42 else
43 return next_multiple_of_alignment(RequestedBlockSize);
44 }
45
46 static constexpr uint32_t MemoryBlockBytes = calculate_block_size();
47 static constexpr uint16_t NBlocks = 48;
48 static constexpr uint16_t NBlocks_Bits = (uint16_t)core::count_bits(NBlocks);
49 static constexpr uint32_t InvalidBlockId = NBlocks + 1;
50#if GAIA_DEBUG
51 static constexpr uint8_t FreedBlockPattern = 0xDD;
52 static constexpr uintptr_t FreedPageMarker = ~(uintptr_t)0;
53#endif
54 static constexpr uint32_t BlockArrayBytes = ((uint32_t)NBlocks_Bits * (uint32_t)NBlocks + 7) / 8;
55
59
62
64 uint32_t m_blockCnt : NBlocks_Bits;
66 uint32_t m_usedBlocks : NBlocks_Bits;
68 uint32_t m_nextFreeBlock : NBlocks_Bits;
70 uint32_t m_freeBlocks : NBlocks_Bits;
72 // uint32_t m_unused : 8;
73
74 MemoryPage(void* ptr):
76 // One cacheline long on x86. The point is for this to be as small as possible
77 static_assert(sizeof(MemoryPage) <= 64);
78 }
79
80 void write_block_idx(uint32_t blockIdx, uint32_t value) {
81 const uint32_t bitPosition = blockIdx * NBlocks_Bits;
82
83 GAIA_ASSERT(bitPosition < NBlocks * NBlocks_Bits);
84 GAIA_ASSERT(value <= InvalidBlockId);
85
86 BitView{{(uint8_t*)m_blocks.data(), BlockArrayBytes}}.set(bitPosition, (uint8_t)value);
87 }
88
89 uint8_t read_block_idx(uint32_t blockIdx) const {
90 const uint32_t bitPosition = blockIdx * NBlocks_Bits;
91
92 GAIA_ASSERT(bitPosition < NBlocks * NBlocks_Bits);
93
94 return BitView{{(uint8_t*)m_blocks.data(), BlockArrayBytes}}.get(bitPosition);
95 }
96
98 GAIA_NODISCARD void* alloc_block() {
99 auto StoreBlockAddress = [&](uint32_t index) {
100 // Encode info about the block's page in the memory block.
101 // The actual pointer returned is offset by MemoryBlockUsableOffset bytes
102 uint8_t* pMemoryBlock = (uint8_t*)m_data + (index * MemoryBlockBytes);
103 GAIA_ASSERT((uintptr_t)pMemoryBlock % MemoryBlockAlignment == 0);
104 mem::unaligned_ref<uintptr_t>{pMemoryBlock} = (uintptr_t)this;
105 return (void*)(pMemoryBlock + MemoryBlockUsableOffset);
106 };
107
108 // We don't want to go out of range for new blocks
109 GAIA_ASSERT(!full() && "Trying to allocate too many blocks!");
110
111 if (m_freeBlocks == 0U) {
112 const auto index = m_blockCnt;
113 ++m_usedBlocks;
114 ++m_blockCnt;
115 write_block_idx(index, index);
116
117 return StoreBlockAddress(index);
118 }
119
120 GAIA_ASSERT(m_nextFreeBlock < m_blockCnt && "Block allocator recycle list broken!");
121
122 ++m_usedBlocks;
123 --m_freeBlocks;
124
125 const auto index = m_nextFreeBlock;
126 m_nextFreeBlock = read_block_idx(m_nextFreeBlock);
127
128 return StoreBlockAddress(index);
129 }
130
132 void free_block(void* pBlock) {
133 GAIA_ASSERT(m_usedBlocks > 0);
134 GAIA_ASSERT(m_freeBlocks <= NBlocks);
135
136 auto ReadBlockAddress = [&](void* pMemory) {
137 // Offset the chunk memory so we get the real block address
138 const auto* pMemoryBlock = (uint8_t*)pMemory - MemoryBlockUsableOffset;
139#if GAIA_ASSERT_ENABLED
140 const auto pageAddr = (uintptr_t)mem::unaligned_ref<uintptr_t>{(void*)pMemoryBlock};
141 GAIA_ASSERT(pageAddr == (uintptr_t)this);
142#endif
143 const auto blckAddr = (uintptr_t)pMemoryBlock;
144 GAIA_ASSERT(blckAddr % 16 == 0);
145 const auto dataAddr = (uintptr_t)m_data;
146 const auto blockIdx = (uint32_t)((blckAddr - dataAddr) / MemoryBlockBytes);
147 return blockIdx;
148 };
149 const auto blockIdx = ReadBlockAddress(pBlock);
150
151#if GAIA_DEBUG
152 mem::unaligned_ref<uintptr_t>{(uint8_t*)pBlock - MemoryBlockUsableOffset} = FreedPageMarker;
153 std::memset(pBlock, FreedBlockPattern, MemoryBlockBytes - MemoryBlockUsableOffset);
154#endif
155
156 // Update our implicit list
157 if (m_freeBlocks == 0U)
158 write_block_idx(blockIdx, InvalidBlockId);
159 else
160 write_block_idx(blockIdx, m_nextFreeBlock);
161 m_nextFreeBlock = blockIdx;
162
163 ++m_freeBlocks;
164 --m_usedBlocks;
165 }
166
167 GAIA_NODISCARD uint32_t used_blocks_cnt() const {
168 return m_usedBlocks;
169 }
170
171 GAIA_NODISCARD bool full() const {
172 return used_blocks_cnt() >= NBlocks;
173 }
174
175 GAIA_NODISCARD bool empty() const {
176 return used_blocks_cnt() == 0;
177 }
178
179 void verify() const {
180#if GAIA_ASSERT_ENABLED
181 GAIA_ASSERT(m_blockCnt <= NBlocks);
182 GAIA_ASSERT(m_usedBlocks <= m_blockCnt);
183 GAIA_ASSERT(m_freeBlocks <= m_blockCnt);
184 GAIA_ASSERT(m_usedBlocks + m_freeBlocks == m_blockCnt);
185 GAIA_ASSERT(((uintptr_t)m_data % MemoryBlockAlignment) == 0);
186
187 uint64_t freeMask = 0;
188 if (m_freeBlocks != 0) {
189 uint32_t next = m_nextFreeBlock;
190 GAIA_FOR(m_freeBlocks) {
191 GAIA_ASSERT(next < m_blockCnt);
192 const auto bit = uint64_t(1) << next;
193 GAIA_ASSERT((freeMask & bit) == 0 && "Free list contains a cycle");
194 freeMask |= bit;
195 next = read_block_idx(next);
196 }
197
198 GAIA_ASSERT(next == InvalidBlockId);
199 }
200
201 GAIA_FOR(m_blockCnt) {
202 const auto* pMemoryBlock = (const uint8_t*)m_data + (i * MemoryBlockBytes);
203 GAIA_ASSERT(((uintptr_t)pMemoryBlock % MemoryBlockAlignment) == 0);
204
205 #if GAIA_DEBUG
206 const bool isFree = (freeMask & (uint64_t(1) << i)) != 0;
207 const auto pageAddr = (uintptr_t)mem::unaligned_ref<uintptr_t>{(void*)pMemoryBlock};
208 GAIA_ASSERT(pageAddr == (isFree ? FreedPageMarker : (uintptr_t)this));
209 #endif
210 }
211#endif
212 }
213 };
214
215 template <typename T, uint32_t RequestedBlockSize>
221
222 GAIA_NODISCARD bool empty() const {
223 return pagesFree.empty() && pagesFull.empty();
224 }
225 };
226
227 struct GAIA_API MemoryPageStats final {
229 uint64_t mem_total;
231 uint64_t mem_used;
233 uint32_t num_pages;
236 };
237
238 namespace detail {
239 template <typename T, uint32_t RequestedBlockSize>
240 class PagedAllocatorImpl;
241 }
242
243 template <typename T, uint32_t RequestedBlockSize = 0>
245
246 namespace detail {
247
248 template <typename T, uint32_t RequestedBlockSize>
250 friend ::gaia::mem::PagedAllocator<T, RequestedBlockSize>;
251
252 inline static char s_strPageData[256]{};
253 inline static char s_strMemPage[256]{};
254
257
259 PageContainer m_pages;
261 bool m_isDone = false;
262
263 private:
265 // PagedAllocatorImpl is only used as a singleton so the constructor is going to be called just once.
266 // Therefore, the strings are only going to be set once.
267 auto ct_name = meta::type_info::name<T>();
268 const auto ct_name_len = (uint32_t)ct_name.size();
269 GAIA_STRCPY(s_strPageData, 256, "PageData_");
270 memcpy((void*)&s_strPageData[9], (const void*)ct_name.data(), ct_name_len);
271 s_strPageData[9 + ct_name_len] = 0;
272 GAIA_STRCPY(s_strMemPage, 256, "MemPage_");
273 memcpy((void*)&s_strMemPage[8], (const void*)ct_name.data(), ct_name_len);
274 s_strMemPage[8 + ct_name_len] = 0;
275 }
276
277 void on_delete() {
278 flush();
279
280 // Make sure there are no leaks
281 auto memStats = stats();
282 if (memStats.mem_total != 0) {
283 GAIA_ASSERT2(false, "Paged allocator leaking memory");
284 GAIA_LOG_W("Paged allocator leaking memory!");
285 diag();
286 }
287 }
288
289 public:
291 on_delete();
292 }
293
294 PagedAllocatorImpl(PagedAllocatorImpl&& world) = delete;
295 PagedAllocatorImpl(const PagedAllocatorImpl& world) = delete;
296 PagedAllocatorImpl& operator=(PagedAllocatorImpl&&) = delete;
297 PagedAllocatorImpl& operator=(const PagedAllocatorImpl&) = delete;
298
300 void* alloc([[maybe_unused]] uint32_t dummy) {
301 void* pBlock = nullptr;
302
303 // Find first page with available space
304 auto* pPage = m_pages.pagesFree.first;
305 GAIA_ASSERT(pPage == nullptr || !pPage->full());
306 if (pPage == nullptr) {
307 // Allocate a new page if no free page was found
308 pPage = alloc_page();
309 m_pages.pagesFree.link(pPage);
310 }
311
312 // Allocate a new chunk of memory
313 pBlock = pPage->alloc_block();
314
315 // Handle full pages
316 if (pPage->full()) {
317 // Remove the page from the open list
318 m_pages.pagesFree.unlink(pPage);
319 // Move our page to the full list
320 m_pages.pagesFull.link(pPage);
321 }
322
323 verify();
324 return pBlock;
325 }
326
327 GAIA_CLANG_WARNING_PUSH()
328 // Memory is aligned so we can silence this warning
329 GAIA_CLANG_WARNING_DISABLE("-Wcast-align")
330
332 void free(void* pBlock) {
333 // Decode the page from the address
334 const auto pageAddr = *(uintptr_t*)((uint8_t*)pBlock - MemoryBlockUsableOffset);
335 GAIA_ASSERT(pageAddr % MemoryBlockAlignment == 0);
336 auto* pPage = (Page*)pageAddr;
337 const bool wasFull = pPage->full();
338
339#if GAIA_ASSERT_ENABLED
340 if (wasFull) {
341 const auto res = m_pages.pagesFull.has(pPage);
342 GAIA_ASSERT(res && "Memory page couldn't be found among full pages");
343 } else {
344 const auto res = m_pages.pagesFree.has(pPage);
345 GAIA_ASSERT(res && "Memory page couldn't be found among free pages");
346 }
347#endif
348
349 // Free the chunk
350 pPage->free_block(pBlock);
351
352 // Update lists
353 if (wasFull) {
354 // Our page is no longer full
355 m_pages.pagesFull.unlink(pPage);
356 // Move our page to the open list
357 m_pages.pagesFree.link(pPage);
358 }
359
360 verify();
361
362 // Special handling for the allocator signaled to destroy itself
363 if (m_isDone) {
364 // Remove the page right away
365 if (pPage->empty()) {
366 GAIA_ASSERT(!m_pages.pagesFree.empty());
367 m_pages.pagesFree.unlink(pPage);
368 }
369
370 try_delete_this();
371 }
372 }
373
374 GAIA_CLANG_WARNING_POP()
375
376
379
380 stats.num_pages = (uint32_t)m_pages.pagesFree.size() + (uint32_t)m_pages.pagesFull.size();
381 stats.num_pages_free = (uint32_t)m_pages.pagesFree.size();
382 stats.mem_total = stats.num_pages * (size_t)Page::MemoryBlockBytes * Page::NBlocks;
383 stats.mem_used = m_pages.pagesFull.size() * (size_t)Page::MemoryBlockBytes * Page::NBlocks;
384 for (const auto& page: m_pages.pagesFree)
385 stats.mem_used += page.used_blocks_cnt() * (size_t)Page::MemoryBlockBytes;
386
387 return stats;
388 }
389
391 void flush() {
392 for (auto it = m_pages.pagesFree.begin(); it != m_pages.pagesFree.end();) {
393 auto* pPage = &(*it);
394 ++it;
395
396 // Skip non-empty pages
397 if (!pPage->empty())
398 continue;
399
400 m_pages.pagesFree.unlink(pPage);
401 free_page(pPage);
402 }
403
404 verify();
405 }
406
408 void diag() const {
409 auto memStats = stats();
410 GAIA_LOG_N("PagedAllocator %p stats", (void*)this);
411 GAIA_LOG_N(" Allocated: %" PRIu64 " B", memStats.mem_total);
412 GAIA_LOG_N(" Used: %" PRIu64 " B", memStats.mem_total - memStats.mem_used);
413 GAIA_LOG_N(" Overhead: %" PRIu64 " B", memStats.mem_used);
414 GAIA_LOG_N(
415 " Utilization: %.1f%%",
416 memStats.mem_total != 0 ? 100.0 * ((double)memStats.mem_used / (double)memStats.mem_total) : 0.0);
417 GAIA_LOG_N(" Pages: %u", memStats.num_pages);
418 GAIA_LOG_N(" Free pages: %u", memStats.num_pages_free);
419 }
420
421 void verify() const {
422#if GAIA_ASSERT_ENABLED
423 for (const auto& page: m_pages.pagesFree) {
424 GAIA_ASSERT(page.get_fwd_llist_link().linked());
425 GAIA_ASSERT(!page.full());
426 page.verify();
427 }
428
429 for (const auto& page: m_pages.pagesFull) {
430 GAIA_ASSERT(page.get_fwd_llist_link().linked());
431 GAIA_ASSERT(page.full());
432 page.verify();
433 }
434#endif
435 }
436
437 private:
438 static Page* alloc_page() {
439 const uint32_t size = Page::NBlocks * Page::MemoryBlockBytes;
440 auto* pPageData = mem::AllocHelper::alloc_alig<uint8_t>(&s_strPageData[0], MemoryBlockAlignment, size);
441 auto* pMemoryPage = mem::AllocHelper::alloc<Page>(&s_strMemPage[0]);
442 return new (pMemoryPage) Page(pPageData);
443 }
444
445 static void free_page(Page* pMemoryPage) {
446 GAIA_ASSERT(pMemoryPage != nullptr);
447
448 mem::AllocHelper::free_alig(&s_strPageData[0], pMemoryPage->m_data);
449 pMemoryPage->~MemoryPage();
450 mem::AllocHelper::free(&s_strMemPage[0], pMemoryPage);
451 }
452
453 void done() {
454 m_isDone = true;
455 }
456
457 void try_delete_this() {
458 // When there is nothing left, delete the allocator
459 if (m_pages.empty())
460 delete this;
461 }
462 };
463
464 } // namespace detail
465 } // namespace mem
466} // namespace gaia
Array with variable size of elements of type.
Definition darray_impl.h:25
Gaia-ECS is a header-only library which means we want to avoid using global static variables because ...
Definition dyn_singleton.h:21
Definition paged_allocator.h:249
void diag() const
Performs diagnostics of the memory used.
Definition paged_allocator.h:408
void free(void *pBlock)
Releases memory allocated for pointer.
Definition paged_allocator.h:332
MemoryPageStats stats() const
Returns allocator statistics.
Definition paged_allocator.h:377
void * alloc(uint32_t dummy)
Allocates memory.
Definition paged_allocator.h:300
void flush()
Flushes unused memory.
Definition paged_allocator.h:391
Pointer wrapper for writing memory in defined way (not causing undefined behavior)
Definition mem_alloc.h:260
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Each fwd_llist node either has to inherit from fwd_llist_base or it has to provide get_fwd_llist_link...
Definition fwd_llist.h:26
Definition bit_utils.h:11
Definition paged_allocator.h:216
cnt::fwd_llist< MemoryPage< T, RequestedBlockSize > > pagesFull
List of full pages.
Definition paged_allocator.h:220
cnt::fwd_llist< MemoryPage< T, RequestedBlockSize > > pagesFree
List of available pages.
Definition paged_allocator.h:218
Definition paged_allocator.h:27
void * m_data
Pointer to data managed by page.
Definition paged_allocator.h:29
Definition paged_allocator.h:227
uint32_t num_pages_free
Number of free pages.
Definition paged_allocator.h:235
uint64_t mem_used
Memory actively used.
Definition paged_allocator.h:231
uint64_t mem_total
Total allocated memory.
Definition paged_allocator.h:229
uint32_t num_pages
Number of allocated pages.
Definition paged_allocator.h:233
Definition paged_allocator.h:35
void free_block(void *pBlock)
Release the block allocated by this page.
Definition paged_allocator.h:132
BlockArray m_blocks
Implicit list of blocks.
Definition paged_allocator.h:61
uint32_t m_nextFreeBlock
Index of the next block to recycle.
Definition paged_allocator.h:68
uint32_t m_freeBlocks
Number of blocks to recycle.
Definition paged_allocator.h:70
uint32_t m_usedBlocks
Number of used blocks out of NBlocks.
Definition paged_allocator.h:66
MemoryPage(void *ptr)
Free bits to use in the future.
Definition paged_allocator.h:74
uint32_t m_blockCnt
Number of blocks in the block array.
Definition paged_allocator.h:64
GAIA_NODISCARD void * alloc_block()
Allocate a new block for this page.
Definition paged_allocator.h:98