Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
chunk_allocator.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <cinttypes>
5#include <cstdint>
6#include <cstring>
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/util/logging.h"
15
16namespace gaia {
17 namespace ecs {
19 namespace detail {
20 struct MemoryBlockHeader final {
21 uintptr_t m_pageAddr = 0;
22 uint32_t m_reserved = 0;
23#if GAIA_DEBUG
24 uint32_t m_requestedBytes = 0;
25#endif
26 };
27
28 class ChunkAllocatorImpl;
29 } // namespace detail
31
33 static constexpr uint32_t MemoryBlockAlignment = 64;
35 static constexpr uint32_t MinMemoryBlockSize = 1024 * 8;
37 static constexpr uint32_t MemoryBlockSizeClasses = 4;
39 static constexpr uint32_t MaxMemoryBlockSize = UINT16_MAX & ~(MemoryBlockAlignment - 1);
42 static constexpr uint32_t MemoryBlockUsableOffset = (uint32_t)sizeof(detail::MemoryBlockHeader);
43
47 constexpr uint16_t mem_block_size(uint32_t sizeType) {
48 constexpr uint16_t sizes[] = {
49 MinMemoryBlockSize, MinMemoryBlockSize * 2, MinMemoryBlockSize * 4, MaxMemoryBlockSize};
50 return sizes[sizeType];
51 }
52
56 constexpr uint8_t mem_block_size_type(uint32_t sizeBytes) {
57 GAIA_ASSERT(sizeBytes > 0);
58 if (sizeBytes <= MinMemoryBlockSize)
59 return 0;
60 if (sizeBytes <= MinMemoryBlockSize * 2)
61 return 1;
62 if (sizeBytes <= MinMemoryBlockSize * 4)
63 return 2;
64 return 3;
65 }
66
67#if GAIA_ECS_CHUNK_ALLOCATOR
68 struct GAIA_API ChunkAllocatorPageStats final {
70 uint64_t mem_total;
72 uint64_t mem_used;
74 uint32_t num_pages;
76 uint32_t num_pages_free;
77 #if GAIA_DEBUG
79 uint64_t mem_requested;
81 uint32_t num_pages_empty;
82 #endif
83 };
84
85 struct GAIA_API ChunkAllocatorStats final {
86 ChunkAllocatorPageStats stats[MemoryBlockSizeClasses];
87 };
88
89 using ChunkAllocator = core::dyn_singleton<detail::ChunkAllocatorImpl>;
90
92 namespace detail {
93 struct MemoryPageHeader {
95 void* m_data;
96
97 MemoryPageHeader(void* ptr): m_data(ptr) {}
98 };
99
100 struct MemoryPage: MemoryPageHeader, cnt::fwd_llist_base<MemoryPage> {
101 static constexpr uint16_t NBlocks = 48;
102 static constexpr uint16_t NBlocks_Bits = (uint16_t)core::count_bits(NBlocks);
103 static constexpr uint32_t InvalidBlockId = NBlocks + 1;
104 #if GAIA_DEBUG
105 static constexpr uint8_t FreedBlockPattern = 0xDD;
106 #endif
107 static constexpr uint32_t BlockArrayBytes = ((uint32_t)NBlocks_Bits * (uint32_t)NBlocks + 7) / 8;
108 using BlockArray = cnt::sarray<uint8_t, BlockArrayBytes>;
109 using BitView = core::bit_view<NBlocks_Bits>;
110
112 BlockArray m_blocks;
113
115 uint32_t m_sizeType : 2;
117 uint32_t m_blockCnt : NBlocks_Bits;
119 uint32_t m_usedBlocks : NBlocks_Bits;
121 uint32_t m_nextFreeBlock : NBlocks_Bits;
123 uint32_t m_freeBlocks : NBlocks_Bits;
125 // uint32_t m_unused : 6;
126
127 #if GAIA_ASSERT_ENABLED
128 uint64_t m_usedMask = 0;
129 #endif
130
131 MemoryPage(void* ptr, uint8_t sizeType):
132 MemoryPageHeader(ptr), m_sizeType(sizeType), m_blockCnt(0), m_usedBlocks(0), m_nextFreeBlock(0),
133 m_freeBlocks(0) {
134 #if GAIA_ASSERT_ENABLED
135 static_assert(sizeof(MemoryPage) <= 72);
136 #else
137 static_assert(sizeof(MemoryPage) <= 64);
138 #endif
139 }
140
141 void write_block_idx(uint32_t blockIdx, uint32_t value) {
142 const uint32_t bitPosition = blockIdx * NBlocks_Bits;
143
144 GAIA_ASSERT(bitPosition < NBlocks * NBlocks_Bits);
145 GAIA_ASSERT(value <= InvalidBlockId);
146
147 BitView{{(uint8_t*)m_blocks.data(), BlockArrayBytes}}.set(bitPosition, (uint8_t)value);
148 }
149
150 uint8_t read_block_idx(uint32_t blockIdx) const {
151 const uint32_t bitPosition = blockIdx * NBlocks_Bits;
152
153 GAIA_ASSERT(bitPosition < NBlocks * NBlocks_Bits);
154
155 return BitView{{(uint8_t*)m_blocks.data(), BlockArrayBytes}}.get(bitPosition);
156 }
157
158 GAIA_NODISCARD void* alloc_block(
159 #if GAIA_DEBUG
160 uint32_t bytesWanted
161 #endif
162 ) {
163 auto StoreBlockAddress = [&](uint32_t index) {
164 // Encode info about chunk's page in the memory block.
165 // The actual pointer returned is offset by MemoryBlockUsableOffset bytes
166 auto* pMemoryBlock = (uint8_t*)m_data + (index * mem_block_size(m_sizeType));
167 GAIA_ASSERT((uintptr_t)pMemoryBlock % MemoryBlockAlignment == 0);
168 auto& header = block_header(pMemoryBlock);
169 header.m_pageAddr = (uintptr_t)this;
170 header.m_reserved = 0;
171 #if GAIA_DEBUG
172 header.m_requestedBytes = bytesWanted;
173 #endif
174 return (void*)(pMemoryBlock + MemoryBlockUsableOffset);
175 };
176
177 // We don't want to go out of range for new blocks
178 GAIA_ASSERT(!full() && "Trying to allocate too many blocks!");
179
180 uint32_t index = 0;
181 if (m_freeBlocks == 0U) {
182 index = m_blockCnt;
183 ++m_usedBlocks;
184 ++m_blockCnt;
185 write_block_idx(index, index);
186 } else {
187 GAIA_ASSERT(m_nextFreeBlock < m_blockCnt && "Block allocator recycle list broken!");
188
189 ++m_usedBlocks;
190 --m_freeBlocks;
191
192 index = m_nextFreeBlock;
193 m_nextFreeBlock = read_block_idx(m_nextFreeBlock);
194 }
195
196 #if GAIA_ASSERT_ENABLED
197 GAIA_ASSERT((m_usedMask & (uint64_t(1) << index)) == 0 && "Block already marked as live");
198 m_usedMask |= uint64_t(1) << index;
199 #endif
200
201 return StoreBlockAddress(index);
202 }
203
204 void free_block(void* pBlock) {
205 GAIA_ASSERT(pBlock != nullptr);
206 GAIA_ASSERT(m_usedBlocks > 0);
207 GAIA_ASSERT(m_freeBlocks <= NBlocks);
208
209 // Offset the chunk memory so we get the real block address
210 const auto* pMemoryBlock = (uint8_t*)pBlock - MemoryBlockUsableOffset;
211 const auto blckAddr = (uintptr_t)pMemoryBlock;
212 GAIA_ASSERT(blckAddr % MemoryBlockAlignment == 0);
213 const auto dataAddr = (uintptr_t)m_data;
214 GAIA_ASSERT(blckAddr >= dataAddr);
215 const auto blockSize = (uintptr_t)mem_block_size(m_sizeType);
216 #if GAIA_ASSERT_ENABLED
217 const auto pageSize = blockSize * NBlocks;
218 GAIA_ASSERT(blckAddr < dataAddr + pageSize);
219 #endif
220 GAIA_ASSERT((blckAddr - dataAddr) % blockSize == 0);
221 const auto blockIdx = (uint32_t)((blckAddr - dataAddr) / blockSize);
222 GAIA_ASSERT(blockIdx < m_blockCnt);
223
224 #if GAIA_DEBUG
225 auto& header = block_header((void*)pMemoryBlock);
226 GAIA_ASSERT(header.m_requestedBytes > 0);
227 #endif
228 #if GAIA_ASSERT_ENABLED
229 GAIA_ASSERT((m_usedMask & (uint64_t(1) << blockIdx)) != 0 && "Double free or corrupted block state");
230 m_usedMask &= ~(uint64_t(1) << blockIdx);
231 #endif
232
233 #if GAIA_DEBUG
234 header.m_requestedBytes = 0;
235 std::memset(pBlock, FreedBlockPattern, blockSize - MemoryBlockUsableOffset);
236 #endif
237
238 // Update our implicit list
239 if (m_freeBlocks == 0U)
240 write_block_idx(blockIdx, InvalidBlockId);
241 else
242 write_block_idx(blockIdx, m_nextFreeBlock);
243 m_nextFreeBlock = blockIdx;
244
245 ++m_freeBlocks;
246 --m_usedBlocks;
247 }
248
249 GAIA_NODISCARD uint32_t used_blocks_cnt() const {
250 return m_usedBlocks;
251 }
252
253 GAIA_NODISCARD bool full() const {
254 return used_blocks_cnt() >= NBlocks;
255 }
256
257 GAIA_NODISCARD bool empty() const {
258 return used_blocks_cnt() == 0;
259 }
260
261 void verify() const {
262 #if GAIA_ASSERT_ENABLED
263 GAIA_ASSERT(m_sizeType < MemoryBlockSizeClasses);
264 GAIA_ASSERT(m_blockCnt <= NBlocks);
265 GAIA_ASSERT(m_usedBlocks <= m_blockCnt);
266 GAIA_ASSERT(m_freeBlocks <= m_blockCnt);
267 GAIA_ASSERT(m_usedBlocks + m_freeBlocks == m_blockCnt);
268
269 const auto blockSize = (uintptr_t)mem_block_size(m_sizeType);
270
271 const auto pageAddr = (uintptr_t)m_data;
272 GAIA_ASSERT(pageAddr % MemoryBlockAlignment == 0);
273
274 #if GAIA_DEBUG
275 uint64_t freeMask = 0;
276 #endif
277
278 if (m_freeBlocks != 0) {
279 uint32_t next = m_nextFreeBlock;
280 GAIA_FOR(m_freeBlocks) {
281 GAIA_ASSERT(next < m_blockCnt);
282 #if GAIA_DEBUG
283 const auto bit = uint64_t(1) << next;
284 GAIA_ASSERT((freeMask & bit) == 0 && "Free list contains a cycle");
285 freeMask |= bit;
286 #endif
287 next = read_block_idx(next);
288 }
289
290 GAIA_ASSERT(next == InvalidBlockId);
291 }
292
293 GAIA_FOR(m_blockCnt) {
294 const auto* pMemoryBlock = (const uint8_t*)m_data + (i * blockSize);
295 const auto& header = block_header(pMemoryBlock);
296 GAIA_ASSERT(header.m_pageAddr == (uintptr_t)this);
297 GAIA_ASSERT(((uintptr_t)pMemoryBlock % MemoryBlockAlignment) == 0);
298
299 #if GAIA_DEBUG
300 const bool isFree = (freeMask & (uint64_t(1) << i)) != 0;
301 GAIA_ASSERT((header.m_requestedBytes == 0) == isFree);
302 #endif
303 }
304
305 #if GAIA_DEBUG
306 GAIA_ASSERT((m_usedMask & freeMask) == 0);
307 const auto liveMask = m_blockCnt == 64 ? ~uint64_t(0) : ((uint64_t(1) << m_blockCnt) - 1);
308 GAIA_ASSERT((m_usedMask | freeMask) == liveMask);
309 #endif
310 #endif
311 }
312
313 #if GAIA_DEBUG
314 GAIA_NODISCARD uint64_t requested_bytes() const {
315 if (m_usedBlocks == 0)
316 return 0;
317
318 uint64_t freeMask = 0;
319 uint32_t next = m_nextFreeBlock;
320 GAIA_FOR(m_freeBlocks) {
321 GAIA_ASSERT(next < m_blockCnt);
322 const auto bit = uint64_t(1) << next;
323 GAIA_ASSERT((freeMask & bit) == 0 && "Free list contains a cycle");
324 freeMask |= bit;
325 next = read_block_idx(next);
326 }
327
328 uint64_t requested = 0;
329 GAIA_FOR(m_blockCnt) {
330 if ((freeMask & (uint64_t(1) << i)) != 0)
331 continue;
332
333 const auto* pMemoryBlock = (const uint8_t*)m_data + (i * mem_block_size(m_sizeType));
334 requested += block_header(pMemoryBlock).m_requestedBytes;
335 }
336
337 return requested;
338 }
339 #endif
340
341 private:
342 static MemoryBlockHeader& block_header(void* pMemoryBlock) {
343 return *(MemoryBlockHeader*)pMemoryBlock;
344 }
345
346 static const MemoryBlockHeader& block_header(const void* pMemoryBlock) {
347 return *(const MemoryBlockHeader*)pMemoryBlock;
348 }
349 };
350
351 enum class MemoryPageState : uint8_t { Detached, Empty, Partial, Full };
352
353 struct MemoryPageContainer {
355 cnt::fwd_llist<MemoryPage> pagesEmpty;
357 cnt::fwd_llist<MemoryPage> pagesPartial;
359 cnt::fwd_llist<MemoryPage> pagesFull;
360
361 GAIA_NODISCARD bool empty() const {
362 return pagesEmpty.empty() && pagesPartial.empty() && pagesFull.empty();
363 }
364 };
365
367 class ChunkAllocatorImpl {
368 friend ::gaia::ecs::ChunkAllocator;
369
371 MemoryPageContainer m_pages[MemoryBlockSizeClasses];
372
374 bool m_isDone = false;
375
376 private:
377 ChunkAllocatorImpl() = default;
378
379 void on_delete() {
380 flush(true);
381
382 // Make sure there are no leaks
383 auto memStats = stats();
384 for (const auto& s: memStats.stats) {
385 if (s.mem_total != 0) {
386 GAIA_ASSERT2(false, "ECS leaking memory");
387 GAIA_LOG_W("ECS leaking memory!");
388 diag();
389 }
390 }
391 }
392
393 public:
394 ~ChunkAllocatorImpl() {
395 on_delete();
396 }
397
398 ChunkAllocatorImpl(ChunkAllocatorImpl&& world) = delete;
399 ChunkAllocatorImpl(const ChunkAllocatorImpl& world) = delete;
400 ChunkAllocatorImpl& operator=(ChunkAllocatorImpl&&) = delete;
401 ChunkAllocatorImpl& operator=(const ChunkAllocatorImpl&) = delete;
402
404 void* alloc(uint32_t bytesWanted) {
405 GAIA_ASSERT(bytesWanted > 0);
406 GAIA_ASSERT(bytesWanted <= MaxMemoryBlockSize);
407 if (bytesWanted == 0 || bytesWanted > MaxMemoryBlockSize)
408 return nullptr;
409
410 const ::gaia::mem::detail::ArenaLock arenaLock;
411 const auto sizeType = mem_block_size_type(bytesWanted);
412 auto& container = m_pages[sizeType];
413
414 MemoryPageState prevState = MemoryPageState::Partial;
415 auto* pPage = container.pagesPartial.first;
416 if (pPage == nullptr) {
417 prevState = MemoryPageState::Empty;
418 pPage = container.pagesEmpty.first;
419 if (pPage == nullptr) {
420 prevState = MemoryPageState::Detached;
421 pPage = alloc_page(sizeType);
422 }
423 }
424
425 // Allocate a new chunk of memory
426 #if GAIA_DEBUG
427 void* pBlock = pPage->alloc_block(bytesWanted);
428 #else
429 void* pBlock = pPage->alloc_block();
430 #endif
431
432 move_page(container, pPage, prevState, state_for(*pPage));
433 verify();
434 return pBlock;
435 }
436
437 GAIA_CLANG_WARNING_PUSH()
438 // Memory is aligned so we can silence this warning
439 GAIA_CLANG_WARNING_DISABLE("-Wcast-align")
440
442 void free(void* pBlock) {
443 GAIA_ASSERT(pBlock != nullptr);
444 if (pBlock == nullptr)
445 return;
446
447 const ::gaia::mem::detail::ArenaLock arenaLock;
448 // Decode the page from the address
449 const auto& header = *(const MemoryBlockHeader*)((uint8_t*)pBlock - MemoryBlockUsableOffset);
450 const auto pageAddr = header.m_pageAddr;
451 GAIA_ASSERT(pageAddr % sizeof(uintptr_t) == 0);
452 #if GAIA_DEBUG
453 GAIA_ASSERT(header.m_requestedBytes > 0);
454 #endif
455 auto* pPage = (MemoryPage*)pageAddr;
456 const auto prevState = state_for(*pPage);
457
458 auto& container = m_pages[pPage->m_sizeType];
459
460 #if GAIA_ASSERT_ENABLED
461 if (prevState == MemoryPageState::Full) {
462 const auto res = container.pagesFull.has(pPage);
463 GAIA_ASSERT(res && "Memory page couldn't be found among full pages");
464 } else if (prevState == MemoryPageState::Partial) {
465 const auto res = container.pagesPartial.has(pPage);
466 GAIA_ASSERT(res && "Memory page couldn't be found among partial pages");
467 } else {
468 GAIA_ASSERT(false && "Allocated block can't belong to an empty page");
469 }
470 #endif
471
472 // Free the chunk
473 pPage->free_block(pBlock);
474
475 // Update lists
476 move_page(container, pPage, prevState, state_for(*pPage));
477 verify();
478
479 // Special handling for the allocator signaled to destroy itself
480 if (m_isDone) {
481 if (pPage->empty()) {
482 container.pagesEmpty.unlink(pPage);
483 free_page(pPage);
484 }
485
486 try_delete_this();
487 }
488 }
489
490 GAIA_CLANG_WARNING_POP()
491
492
493 ChunkAllocatorStats stats() const {
494 ChunkAllocatorStats stats{};
495 for (uint32_t sizeType = 0; sizeType < MemoryBlockSizeClasses; ++sizeType)
496 stats.stats[sizeType] = page_stats(sizeType);
497 return stats;
498 }
499
502 void flush(bool releaseAll = false) {
503 const ::gaia::mem::detail::ArenaLock arenaLock;
504 uint32_t i = 0;
505 for (auto& page: m_pages)
506 flushPages(page, i++, releaseAll);
507 verify();
508 }
509
511 void diag() const {
512 auto diagPage = [](const ChunkAllocatorPageStats& stats, uint32_t sizeType) {
513 GAIA_LOG_N("ChunkAllocator %uK stats", mem_block_size(sizeType) / 1024);
514 GAIA_LOG_N(" Allocated: %" PRIu64 " B", stats.mem_total);
515 GAIA_LOG_N(" Reserved by live blocks: %" PRIu64 " B", stats.mem_used);
516 GAIA_LOG_N(" Pages: %u", stats.num_pages);
517 GAIA_LOG_N(" Reusable pages: %u", stats.num_pages_free);
518 #if !GAIA_DEBUG
519 GAIA_LOG_N(
520 " Utilization: %.1f%%",
521 stats.mem_total ? 100.0 * ((double)stats.mem_used / (double)stats.mem_total) : 0);
522 #else
523 GAIA_LOG_N(" Requested: %" PRIu64 " B", stats.mem_requested);
524 GAIA_LOG_N(" Free capacity: %" PRIu64 " B", stats.mem_total - stats.mem_used);
525 GAIA_LOG_N(" Internal slack: %" PRIu64 " B", stats.mem_used - stats.mem_requested);
526 GAIA_LOG_N(
527 " Utilization: %.1f%%",
528 stats.mem_total ? 100.0 * ((double)stats.mem_requested / (double)stats.mem_total) : 0);
529 GAIA_LOG_N(" Empty pages: %u", stats.num_pages_empty);
530 #endif
531 };
532
533 auto memStats = stats();
534 for (uint32_t sizeType = 0; sizeType < MemoryBlockSizeClasses; ++sizeType)
535 diagPage(memStats.stats[sizeType], sizeType);
536 }
537
538 void verify() const {
539 #if GAIA_ASSERT_ENABLED
540 for (uint32_t sizeType = 0; sizeType < MemoryBlockSizeClasses; ++sizeType)
541 verify_container(m_pages[sizeType], sizeType);
542 #endif
543 }
544
545 private:
546 static constexpr const char* s_strChunkAlloc_Chunk = "Chunk";
547 static constexpr const char* s_strChunkAlloc_MemPage = "MemoryPage";
548
549 static MemoryPage* alloc_page(uint8_t sizeType) {
550 const uint32_t size = mem_block_size(sizeType) * MemoryPage::NBlocks;
551 auto* pPageData = mem::AllocHelper::alloc_alig<uint8_t>(s_strChunkAlloc_Chunk, MemoryBlockAlignment, size);
552 auto* pMemoryPage = mem::AllocHelper::alloc<MemoryPage>(s_strChunkAlloc_MemPage);
553 return new (pMemoryPage) MemoryPage(pPageData, sizeType);
554 }
555
556 static void free_page(MemoryPage* pMemoryPage) {
557 GAIA_ASSERT(pMemoryPage != nullptr);
558
559 mem::AllocHelper::free_alig(s_strChunkAlloc_Chunk, pMemoryPage->m_data);
560 pMemoryPage->~MemoryPage();
561 mem::AllocHelper::free(s_strChunkAlloc_MemPage, pMemoryPage);
562 }
563
564 void done() {
565 m_isDone = true;
566 }
567
568 void try_delete_this() {
569 // When there is nothing left, delete the allocator
570 bool allEmpty = true;
571 for (const auto& c: m_pages)
572 allEmpty = allEmpty && c.empty();
573 if (allEmpty)
574 delete this;
575 }
576
577 static constexpr uint32_t warm_pages_to_keep(uint32_t sizeType) {
578 constexpr uint8_t WarmPagesPerSizeClass[] = {1, 1, 0, 0};
579 return WarmPagesPerSizeClass[sizeType];
580 }
581
582 static MemoryPageState state_for(const MemoryPage& page) {
583 if (page.empty())
584 return MemoryPageState::Empty;
585 if (page.full())
586 return MemoryPageState::Full;
587 return MemoryPageState::Partial;
588 }
589
590 static cnt::fwd_llist<MemoryPage>& page_list(MemoryPageContainer& container, MemoryPageState state) {
591 switch (state) {
592 case MemoryPageState::Empty:
593 return container.pagesEmpty;
594 case MemoryPageState::Partial:
595 return container.pagesPartial;
596 default:
597 GAIA_ASSERT(state == MemoryPageState::Full);
598 return container.pagesFull;
599 }
600 }
601
602 static void move_page(
603 MemoryPageContainer& container, MemoryPage* pPage, MemoryPageState fromState, MemoryPageState toState) {
604 if (fromState == toState)
605 return;
606
607 if (fromState != MemoryPageState::Detached)
608 page_list(container, fromState).unlink(pPage);
609 page_list(container, toState).link(pPage);
610 }
611
612 [[maybe_unused]] static void verify_page_membership(
613 [[maybe_unused]] const MemoryPageContainer& container, //
614 [[maybe_unused]] const MemoryPage& page, //
615 [[maybe_unused]] MemoryPageState expectedState //
616 ) {
617 (void)container;
618 GAIA_ASSERT(state_for(page) == expectedState);
619 GAIA_ASSERT(page.get_fwd_llist_link().linked());
620 }
621
622 static void verify_container(const MemoryPageContainer& container, uint32_t sizeType) {
623 (void)sizeType;
624 for (const auto& page: container.pagesEmpty) {
625 GAIA_ASSERT(page.m_sizeType == sizeType);
626 verify_page_membership(container, page, MemoryPageState::Empty);
627 page.verify();
628 }
629
630 for (const auto& page: container.pagesPartial) {
631 GAIA_ASSERT(page.m_sizeType == sizeType);
632 verify_page_membership(container, page, MemoryPageState::Partial);
633 page.verify();
634 }
635
636 for (const auto& page: container.pagesFull) {
637 GAIA_ASSERT(page.m_sizeType == sizeType);
638 verify_page_membership(container, page, MemoryPageState::Full);
639 page.verify();
640 }
641 }
642
643 ChunkAllocatorPageStats page_stats(uint32_t sizeType) const {
644 ChunkAllocatorPageStats stats{};
645 const auto& container = m_pages[sizeType];
646 const auto blockSize = (uint64_t)mem_block_size(sizeType);
647 const auto pageSize = blockSize * MemoryPage::NBlocks;
648
649 stats.num_pages = (uint32_t)container.pagesEmpty.size() + (uint32_t)container.pagesPartial.size() +
650 (uint32_t)container.pagesFull.size();
651 stats.num_pages_free = (uint32_t)container.pagesEmpty.size() + (uint32_t)container.pagesPartial.size();
652 stats.mem_total = stats.num_pages * pageSize;
653 stats.mem_used = container.pagesFull.size() * pageSize;
654
655 #if GAIA_DEBUG
656 stats.num_pages_empty = (uint32_t)container.pagesEmpty.size();
657
658 for (const auto& page: container.pagesFull)
659 stats.mem_requested += page.requested_bytes();
660
661 for (const auto& page: container.pagesPartial) {
662 stats.mem_used += page.used_blocks_cnt() * blockSize;
663 stats.mem_requested += page.requested_bytes();
664 }
665 #else
666 for (const auto& page: container.pagesPartial)
667 stats.mem_used += page.used_blocks_cnt() * blockSize;
668 #endif
669
670 return stats;
671 }
672
675 void flushPages(MemoryPageContainer& container, uint32_t sizeType, bool releaseAll) {
676 const bool keepWarmPage = !releaseAll && warm_pages_to_keep(sizeType) != 0;
677 bool keptWarmPage = false;
678 for (auto it = container.pagesEmpty.begin(); it != container.pagesEmpty.end();) {
679 auto* pPage = &(*it);
680 ++it;
681
682 // Skip non-empty pages
683 if (!pPage->empty())
684 continue;
685
686 if (keepWarmPage && !keptWarmPage) {
687 keptWarmPage = true;
688 continue;
689 }
690
691 container.pagesEmpty.unlink(pPage);
692 free_page(pPage);
693 }
694 }
695 };
696 } // namespace detail
698
699#endif
700
701 } // namespace ecs
702} // namespace gaia