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 <type_traits>
7
8#include "gaia/cnt/fwd_llist.h"
9#include "gaia/cnt/sarray.h"
10#include "gaia/core/bit_utils.h"
11#include "gaia/core/dyn_singleton.h"
12#include "gaia/core/utility.h"
13#include "gaia/mem/mem_alloc.h"
14#include "gaia/meta/type_info.h"
15#include "gaia/util/logging.h"
16
17namespace gaia {
18 namespace mem {
19 static constexpr uint32_t MemoryBlockAlignment = 16;
22 static constexpr uint32_t MemoryBlockBytesDefault = 32768;
24 static constexpr uint32_t MemoryBlockUsableOffset = sizeof(uintptr_t);
25
26 struct GAIA_API MemoryPageHeader {
28 void* m_data;
29
30 MemoryPageHeader(void* ptr): m_data(ptr) {}
31 };
32
33 template <typename T, uint32_t RequestedBlockSize>
34 struct MemoryPage: MemoryPageHeader, cnt::fwd_llist_base<MemoryPage<T, RequestedBlockSize>> {
35 static constexpr uint32_t next_multiple_of_alignment(uint32_t num) {
36 return (num + (MemoryBlockAlignment - 1)) & uint32_t(-(int32_t)MemoryBlockAlignment);
37 }
38 static constexpr uint32_t calculate_block_size() {
39 if constexpr (RequestedBlockSize == 0)
40 return next_multiple_of_alignment(MemoryBlockBytesDefault);
41 else
42 return next_multiple_of_alignment(RequestedBlockSize);
43 }
44
45 static constexpr uint32_t MemoryBlockBytes = calculate_block_size();
46 static constexpr uint16_t NBlocks = 48;
47 static constexpr uint16_t NBlocks_Bits = (uint16_t)core::count_bits(NBlocks);
48 static constexpr uint32_t InvalidBlockId = NBlocks + 1;
49 static constexpr uint32_t BlockArrayBytes = ((uint32_t)NBlocks_Bits * (uint32_t)NBlocks + 7) / 8;
50
54
57
59 uint32_t m_blockCnt : NBlocks_Bits;
61 uint32_t m_usedBlocks : NBlocks_Bits;
63 uint32_t m_nextFreeBlock : NBlocks_Bits;
65 uint32_t m_freeBlocks : NBlocks_Bits;
67 // uint32_t m_unused : 8;
68
69 MemoryPage(void* ptr):
71 // One cacheline long on x86. The point is for this to be as small as possible
72 static_assert(sizeof(MemoryPage) <= 64);
73 }
74
75 void write_block_idx(uint32_t blockIdx, uint32_t value) {
76 const uint32_t bitPosition = blockIdx * NBlocks_Bits;
77
78 GAIA_ASSERT(bitPosition < NBlocks * NBlocks_Bits);
79 GAIA_ASSERT(value <= InvalidBlockId);
80
81 BitView{{(uint8_t*)m_blocks.data(), BlockArrayBytes}}.set(bitPosition, (uint8_t)value);
82 }
83
84 uint8_t read_block_idx(uint32_t blockIdx) const {
85 const uint32_t bitPosition = blockIdx * NBlocks_Bits;
86
87 GAIA_ASSERT(bitPosition < NBlocks * NBlocks_Bits);
88
89 return BitView{{(uint8_t*)m_blocks.data(), BlockArrayBytes}}.get(bitPosition);
90 }
91
93 GAIA_NODISCARD void* alloc_block() {
94 auto StoreBlockAddress = [&](uint32_t index) {
95 // Encode info about the block's page in the memory block.
96 // The actual pointer returned is offset by MemoryBlockUsableOffset bytes
97 uint8_t* pMemoryBlock = (uint8_t*)m_data + (index * MemoryBlockBytes);
98 GAIA_ASSERT((uintptr_t)pMemoryBlock % MemoryBlockAlignment == 0);
99 mem::unaligned_ref<uintptr_t>{pMemoryBlock} = (uintptr_t)this;
100 return (void*)(pMemoryBlock + MemoryBlockUsableOffset);
101 };
102
103 // We don't want to go out of range for new blocks
104 GAIA_ASSERT(!full() && "Trying to allocate too many blocks!");
105
106 if (m_freeBlocks == 0U) {
107 const auto index = m_blockCnt;
108 ++m_usedBlocks;
109 ++m_blockCnt;
110 write_block_idx(index, index);
111
112 return StoreBlockAddress(index);
113 }
114
115 GAIA_ASSERT(m_nextFreeBlock < m_blockCnt && "Block allocator recycle list broken!");
116
117 ++m_usedBlocks;
118 --m_freeBlocks;
119
120 const auto index = m_nextFreeBlock;
121 m_nextFreeBlock = read_block_idx(m_nextFreeBlock);
122
123 return StoreBlockAddress(index);
124 }
125
127 void free_block(void* pBlock) {
128 GAIA_ASSERT(m_usedBlocks > 0);
129 GAIA_ASSERT(m_freeBlocks <= NBlocks);
130
131 auto ReadBlockAddress = [&](void* pMemory) {
132 // Offset the chunk memory so we get the real block address
133 const auto* pMemoryBlock = (uint8_t*)pMemory - MemoryBlockUsableOffset;
134 // Page pointer is written to the start of the memory block
135 [[maybe_unused]] const auto* pPage = (Page**)pMemoryBlock;
136 GAIA_ASSERT(*pPage == this);
137 const auto blckAddr = (uintptr_t)pMemoryBlock;
138 GAIA_ASSERT(blckAddr % 16 == 0);
139 const auto dataAddr = (uintptr_t)m_data;
140 const auto blockIdx = (uint32_t)((blckAddr - dataAddr) / MemoryBlockBytes);
141 return blockIdx;
142 };
143 const auto blockIdx = ReadBlockAddress(pBlock);
144
145 // Update our implicit list
146 if (m_freeBlocks == 0U)
147 write_block_idx(blockIdx, InvalidBlockId);
148 else
149 write_block_idx(blockIdx, m_nextFreeBlock);
150 m_nextFreeBlock = blockIdx;
151
152 ++m_freeBlocks;
153 --m_usedBlocks;
154 }
155
156 GAIA_NODISCARD uint32_t used_blocks_cnt() const {
157 return m_usedBlocks;
158 }
159
160 GAIA_NODISCARD bool full() const {
161 return used_blocks_cnt() >= NBlocks;
162 }
163
164 GAIA_NODISCARD bool empty() const {
165 return used_blocks_cnt() == 0;
166 }
167 };
168
169 template <typename T, uint32_t RequestedBlockSize>
175
176 GAIA_NODISCARD bool empty() const {
177 return pagesFree.empty() && pagesFull.empty();
178 }
179 };
180
181 struct GAIA_API MemoryPageStats final {
183 uint64_t mem_total;
185 uint64_t mem_used;
187 uint32_t num_pages;
190 };
191
192 namespace detail {
193 template <typename T, uint32_t RequestedBlockSize>
194 class PagedAllocatorImpl;
195 }
196
197 template <typename T, uint32_t RequestedBlockSize = 0>
199
200 namespace detail {
201
202 template <typename T, uint32_t RequestedBlockSize>
204 friend ::gaia::mem::PagedAllocator<T, RequestedBlockSize>;
205
206 inline static char s_strPageData[256]{};
207 inline static char s_strMemPage[256]{};
208
211
213 PageContainer m_pages;
215 bool m_isDone = false;
216
217 private:
219 // PagedAllocatorImpl is only used as a singleton so the constructor is going to be called just once.
220 // Therefore, the strings are only going to be set once.
221 auto ct_name = meta::type_info::name<T>();
222 const auto ct_name_len = (uint32_t)ct_name.size();
223 GAIA_STRCPY(s_strPageData, 256, "PageData_");
224 memcpy((void*)&s_strPageData[9], (const void*)ct_name.data(), ct_name_len);
225 s_strPageData[9 + ct_name_len] = 0;
226 GAIA_STRCPY(s_strMemPage, 256, "MemPage_");
227 memcpy((void*)&s_strMemPage[8], (const void*)ct_name.data(), ct_name_len);
228 s_strMemPage[8 + ct_name_len] = 0;
229 }
230
231 void on_delete() {
232 flush();
233
234 // Make sure there are no leaks
235 auto memStats = stats();
236 if (memStats.mem_total != 0) {
237 GAIA_ASSERT2(false, "Paged allocator leaking memory");
238 GAIA_LOG_W("Paged allocator leaking memory!");
239 diag();
240 }
241 }
242
243 public:
245 on_delete();
246 }
247
248 PagedAllocatorImpl(PagedAllocatorImpl&& world) = delete;
249 PagedAllocatorImpl(const PagedAllocatorImpl& world) = delete;
250 PagedAllocatorImpl& operator=(PagedAllocatorImpl&&) = delete;
251 PagedAllocatorImpl& operator=(const PagedAllocatorImpl&) = delete;
252
254 void* alloc([[maybe_unused]] uint32_t dummy) {
255 void* pBlock = nullptr;
256
257 // Find first page with available space
258 auto* pPage = m_pages.pagesFree.first;
259 GAIA_ASSERT(pPage == nullptr || !pPage->full());
260 if (pPage == nullptr) {
261 // Allocate a new page if no free page was found
262 pPage = alloc_page();
263 m_pages.pagesFree.link(pPage);
264 }
265
266 // Allocate a new chunk of memory
267 pBlock = pPage->alloc_block();
268
269 // Handle full pages
270 if (pPage->full()) {
271 // Remove the page from the open list
272 m_pages.pagesFree.unlink(pPage);
273 // Move our page to the full list
274 m_pages.pagesFull.link(pPage);
275 }
276
277 return pBlock;
278 }
279
280 GAIA_CLANG_WARNING_PUSH()
281 // Memory is aligned so we can silence this warning
282 GAIA_CLANG_WARNING_DISABLE("-Wcast-align")
283
285 void free(void* pBlock) {
286 // Decode the page from the address
287 const auto pageAddr = *(uintptr_t*)((uint8_t*)pBlock - MemoryBlockUsableOffset);
288 GAIA_ASSERT(pageAddr % MemoryBlockAlignment == 0);
289 auto* pPage = (Page*)pageAddr;
290 const bool wasFull = pPage->full();
291
292#if GAIA_ASSERT_ENABLED
293 if (wasFull) {
294 const auto res = m_pages.pagesFull.has(pPage);
295 GAIA_ASSERT(res && "Memory page couldn't be found among full pages");
296 } else {
297 const auto res = m_pages.pagesFree.has(pPage);
298 GAIA_ASSERT(res && "Memory page couldn't be found among free pages");
299 }
300#endif
301
302 // Free the chunk
303 pPage->free_block(pBlock);
304
305 // Update lists
306 if (wasFull) {
307 // Our page is no longer full
308 m_pages.pagesFull.unlink(pPage);
309 // Move our page to the open list
310 m_pages.pagesFree.link(pPage);
311 }
312
313 // Special handling for the allocator signaled to destroy itself
314 if (m_isDone) {
315 // Remove the page right away
316 if (pPage->empty()) {
317 GAIA_ASSERT(!m_pages.pagesFree.empty());
318 m_pages.pagesFree.unlink(pPage);
319 }
320
321 try_delete_this();
322 }
323 }
324
325 GAIA_CLANG_WARNING_POP()
326
327
330
331 stats.num_pages = (uint32_t)m_pages.pagesFree.size() + (uint32_t)m_pages.pagesFull.size();
332 stats.num_pages_free = (uint32_t)m_pages.pagesFree.size();
333 stats.mem_total = stats.num_pages * (size_t)Page::MemoryBlockBytes * Page::NBlocks;
334 stats.mem_used = m_pages.pagesFull.size() * (size_t)Page::MemoryBlockBytes * Page::NBlocks;
335 for (const auto& page: m_pages.pagesFree)
336 stats.mem_used += page.used_blocks_cnt() * (size_t)Page::MemoryBlockBytes;
337
338 return stats;
339 }
340
342 void flush() {
343 for (auto it = m_pages.pagesFree.begin(); it != m_pages.pagesFree.end();) {
344 auto* pPage = &(*it);
345 ++it;
346
347 // Skip non-empty pages
348 if (!pPage->empty())
349 continue;
350
351 m_pages.pagesFree.unlink(pPage);
352 free_page(pPage);
353 }
354 }
355
357 void diag() const {
358 auto memStats = stats();
359 GAIA_LOG_N("PagedAllocator %p stats", (void*)this);
360 GAIA_LOG_N(" Allocated: %" PRIu64 " B", memStats.mem_total);
361 GAIA_LOG_N(" Used: %" PRIu64 " B", memStats.mem_total - memStats.mem_used);
362 GAIA_LOG_N(" Overhead: %" PRIu64 " B", memStats.mem_used);
363 GAIA_LOG_N(
364 " Utilization: %.1f%%",
365 memStats.mem_total != 0 ? 100.0 * ((double)memStats.mem_used / (double)memStats.mem_total) : 0.0);
366 GAIA_LOG_N(" Pages: %u", memStats.num_pages);
367 GAIA_LOG_N(" Free pages: %u", memStats.num_pages_free);
368 }
369
370 private:
371 static Page* alloc_page() {
372 const uint32_t size = Page::NBlocks * Page::MemoryBlockBytes;
373 auto* pPageData = mem::AllocHelper::alloc_alig<uint8_t>(&s_strPageData[0], MemoryBlockAlignment, size);
374 auto* pMemoryPage = mem::AllocHelper::alloc<Page>(&s_strMemPage[0]);
375 return new (pMemoryPage) Page(pPageData);
376 }
377
378 static void free_page(Page* pMemoryPage) {
379 GAIA_ASSERT(pMemoryPage != nullptr);
380
381 mem::AllocHelper::free_alig(&s_strPageData[0], pMemoryPage->m_data);
382 pMemoryPage->~MemoryPage();
383 mem::AllocHelper::free(&s_strMemPage[0], pMemoryPage);
384 }
385
386 void done() {
387 m_isDone = true;
388 }
389
390 void try_delete_this() {
391 // When there is nothing left, delete the allocator
392 if (m_pages.empty())
393 delete this;
394 }
395 };
396
397 } // namespace detail
398 } // namespace mem
399} // namespace gaia
Array of elements of type.
Definition sarray_impl.h:26
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:203
void diag() const
Performs diagnostics of the memory used.
Definition paged_allocator.h:357
void free(void *pBlock)
Releases memory allocated for pointer.
Definition paged_allocator.h:285
MemoryPageStats stats() const
Returns allocator statistics.
Definition paged_allocator.h:328
void * alloc(uint32_t dummy)
Allocates memory.
Definition paged_allocator.h:254
void flush()
Flushes unused memory.
Definition paged_allocator.h:342
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
Forward list container. No memory allocation is performed because the list is stored directly inside ...
Definition fwd_llist.h:85
Definition bit_utils.h:11
Definition paged_allocator.h:170
cnt::fwd_llist< MemoryPage< T, RequestedBlockSize > > pagesFull
List of full pages.
Definition paged_allocator.h:174
cnt::fwd_llist< MemoryPage< T, RequestedBlockSize > > pagesFree
List of available pages.
Definition paged_allocator.h:172
Definition paged_allocator.h:26
void * m_data
Pointer to data managed by page.
Definition paged_allocator.h:28
Definition paged_allocator.h:181
uint32_t num_pages_free
Number of free pages.
Definition paged_allocator.h:189
uint64_t mem_used
Memory actively used.
Definition paged_allocator.h:185
uint64_t mem_total
Total allocated memory.
Definition paged_allocator.h:183
uint32_t num_pages
Number of allocated pages.
Definition paged_allocator.h:187
Definition paged_allocator.h:34
void free_block(void *pBlock)
Release the block allocated by this page.
Definition paged_allocator.h:127
BlockArray m_blocks
Implicit list of blocks.
Definition paged_allocator.h:56
uint32_t m_nextFreeBlock
Index of the next block to recycle.
Definition paged_allocator.h:63
uint32_t m_freeBlocks
Number of blocks to recycle.
Definition paged_allocator.h:65
uint32_t m_usedBlocks
Number of used blocks out of NBlocks.
Definition paged_allocator.h:61
MemoryPage(void *ptr)
Free bits to use in the future.
Definition paged_allocator.h:69
uint32_t m_blockCnt
Number of blocks in the block array.
Definition paged_allocator.h:59
GAIA_NODISCARD void * alloc_block()
Allocate a new block for this page.
Definition paged_allocator.h:93