2#include "gaia/config/config.h"
7#include "gaia/cnt/fwd_llist.h"
8#include "gaia/cnt/sarray.h"
9#include "gaia/core/bit_utils.h"
10#include "gaia/core/dyn_singleton.h"
11#include "gaia/core/utility.h"
12#include "gaia/mem/mem_alloc.h"
13#include "gaia/util/logging.h"
17 static constexpr uint32_t MinMemoryBlockSize = 1024 * 8;
19 static constexpr uint32_t MaxMemoryBlockSize = MinMemoryBlockSize * 4;
21 static constexpr uint32_t MemoryBlockUsableOffset =
sizeof(uintptr_t);
23 constexpr uint16_t mem_block_size(uint32_t sizeType) {
24 constexpr uint16_t sizes[] = {MinMemoryBlockSize, MinMemoryBlockSize * 2, MaxMemoryBlockSize};
25 return sizes[sizeType];
28 constexpr uint8_t mem_block_size_type(uint32_t sizeBytes) {
30 const uint32_t blocks = (sizeBytes + MinMemoryBlockSize) / MinMemoryBlockSize;
31 return blocks > 2 ? 2 :
static_cast<uint8_t
>(blocks - 1);
34#if GAIA_ECS_CHUNK_ALLOCATOR
35 struct GAIA_API ChunkAllocatorPageStats final {
43 uint32_t num_pages_free;
46 struct GAIA_API ChunkAllocatorStats final {
47 ChunkAllocatorPageStats stats[3];
51 class ChunkAllocatorImpl;
53 using ChunkAllocator = core::dyn_singleton<detail::ChunkAllocatorImpl>;
58 class ChunkAllocatorImpl {
59 friend ::gaia::ecs::ChunkAllocator;
61 struct MemoryPageHeader {
65 MemoryPageHeader(
void* ptr): m_data(ptr) {}
68 struct MemoryPage: MemoryPageHeader, cnt::fwd_llist_base<MemoryPage> {
69 static constexpr uint16_t NBlocks = 48;
70 static constexpr uint16_t NBlocks_Bits = (uint16_t)core::count_bits(NBlocks);
71 static constexpr uint32_t InvalidBlockId = NBlocks + 1;
72 static constexpr uint32_t BlockArrayBytes = ((uint32_t)NBlocks_Bits * (uint32_t)NBlocks + 7) / 8;
73 using BlockArray = cnt::sarray<uint8_t, BlockArrayBytes>;
74 using BitView = core::bit_view<NBlocks_Bits>;
80 uint32_t m_sizeType : 2;
82 uint32_t m_blockCnt : NBlocks_Bits;
84 uint32_t m_usedBlocks : NBlocks_Bits;
86 uint32_t m_nextFreeBlock : NBlocks_Bits;
88 uint32_t m_freeBlocks : NBlocks_Bits;
92 MemoryPage(
void* ptr, uint8_t sizeType):
93 MemoryPageHeader(ptr), m_sizeType(sizeType), m_blockCnt(0), m_usedBlocks(0), m_nextFreeBlock(0),
96 static_assert(
sizeof(MemoryPage) <= 64);
99 void write_block_idx(uint32_t blockIdx, uint32_t value) {
100 const uint32_t bitPosition = blockIdx * NBlocks_Bits;
102 GAIA_ASSERT(bitPosition < NBlocks * NBlocks_Bits);
103 GAIA_ASSERT(value <= InvalidBlockId);
105 BitView{{(uint8_t*)m_blocks.data(), BlockArrayBytes}}.set(bitPosition, (uint8_t)value);
108 uint8_t read_block_idx(uint32_t blockIdx)
const {
109 const uint32_t bitPosition = blockIdx * NBlocks_Bits;
111 GAIA_ASSERT(bitPosition < NBlocks * NBlocks_Bits);
113 return BitView{{(uint8_t*)m_blocks.data(), BlockArrayBytes}}.get(bitPosition);
116 GAIA_NODISCARD
void* alloc_block() {
117 auto StoreBlockAddress = [&](uint32_t index) {
120 uint8_t* pMemoryBlock = (uint8_t*)m_data + (index * mem_block_size(m_sizeType));
121 GAIA_ASSERT((uintptr_t)pMemoryBlock %
sizeof(uintptr_t) == 0);
122 mem::unaligned_ref<uintptr_t>{pMemoryBlock} = (uintptr_t)
this;
123 return (
void*)(pMemoryBlock + MemoryBlockUsableOffset);
127 GAIA_ASSERT(!full() &&
"Trying to allocate too many blocks!");
129 if (m_freeBlocks == 0U) {
130 const auto index = m_blockCnt;
133 write_block_idx(index, index);
135 return StoreBlockAddress(index);
138 GAIA_ASSERT(m_nextFreeBlock < m_blockCnt &&
"Block allocator recycle list broken!");
143 const auto index = m_nextFreeBlock;
144 m_nextFreeBlock = read_block_idx(m_nextFreeBlock);
146 return StoreBlockAddress(index);
149 void free_block(
void* pBlock) {
150 GAIA_ASSERT(m_usedBlocks > 0);
151 GAIA_ASSERT(m_freeBlocks <= NBlocks);
154 const auto* pMemoryBlock = (uint8_t*)pBlock - MemoryBlockUsableOffset;
155 const auto blckAddr = (uintptr_t)pMemoryBlock;
156 GAIA_ASSERT(blckAddr %
sizeof(uintptr_t) == 0);
157 const auto dataAddr = (uintptr_t)m_data;
158 const auto blockIdx = (uint32_t)((blckAddr - dataAddr) / mem_block_size(m_sizeType));
161 if (m_freeBlocks == 0U)
162 write_block_idx(blockIdx, InvalidBlockId);
164 write_block_idx(blockIdx, m_nextFreeBlock);
165 m_nextFreeBlock = blockIdx;
171 GAIA_NODISCARD uint32_t used_blocks_cnt()
const {
175 GAIA_NODISCARD
bool full()
const {
176 return used_blocks_cnt() >= NBlocks;
179 GAIA_NODISCARD
bool empty()
const {
180 return used_blocks_cnt() == 0;
184 struct MemoryPageContainer {
186 cnt::fwd_llist<MemoryPage> pagesFree;
188 cnt::fwd_llist<MemoryPage> pagesFull;
190 GAIA_NODISCARD
bool empty()
const {
191 return pagesFree.empty() && pagesFull.empty();
196 MemoryPageContainer m_pages[3];
199 bool m_isDone =
false;
202 ChunkAllocatorImpl() =
default;
208 auto memStats = stats();
209 for (
const auto& s: memStats.stats) {
210 if (s.mem_total != 0) {
211 GAIA_ASSERT2(
false,
"ECS leaking memory");
212 GAIA_LOG_W(
"ECS leaking memory!");
219 ~ChunkAllocatorImpl() {
223 ChunkAllocatorImpl(ChunkAllocatorImpl&& world) =
delete;
224 ChunkAllocatorImpl(
const ChunkAllocatorImpl& world) =
delete;
225 ChunkAllocatorImpl& operator=(ChunkAllocatorImpl&&) =
delete;
226 ChunkAllocatorImpl& operator=(
const ChunkAllocatorImpl&) =
delete;
229 void* alloc(uint32_t bytesWanted) {
230 GAIA_ASSERT(bytesWanted <= MaxMemoryBlockSize);
232 void* pBlock =
nullptr;
233 MemoryPage* pPage =
nullptr;
235 const auto sizeType = mem_block_size_type(bytesWanted);
236 auto& container = m_pages[sizeType];
239 for (
auto& page: container.pagesFree) {
245 if (pPage ==
nullptr) {
247 pPage = alloc_page(sizeType);
248 container.pagesFree.link(pPage);
252 pBlock = pPage->alloc_block();
257 container.pagesFree.unlink(pPage);
259 container.pagesFull.link(pPage);
265 GAIA_CLANG_WARNING_PUSH()
267 GAIA_CLANG_WARNING_DISABLE("-Wcast-align")
270 void free(
void* pBlock) {
272 const auto pageAddr = *(uintptr_t*)((uint8_t*)pBlock - MemoryBlockUsableOffset);
273 GAIA_ASSERT(pageAddr %
sizeof(uintptr_t) == 0);
274 auto* pPage = (MemoryPage*)pageAddr;
275 const bool wasFull = pPage->full();
277 auto& container = m_pages[pPage->m_sizeType];
279 #if GAIA_ASSERT_ENABLED
281 const auto res = container.pagesFull.has(pPage);
282 GAIA_ASSERT(res &&
"Memory page couldn't be found among full pages");
284 const auto res = container.pagesFree.has(pPage);
285 GAIA_ASSERT(res &&
"Memory page couldn't be found among free pages");
290 pPage->free_block(pBlock);
295 container.pagesFull.unlink(pPage);
297 container.pagesFree.link(pPage);
303 if (pPage->empty()) {
304 GAIA_ASSERT(!container.pagesFree.empty());
305 container.pagesFree.unlink(pPage);
312 GAIA_CLANG_WARNING_POP()
315 ChunkAllocatorStats stats()
const {
316 ChunkAllocatorStats stats;
317 stats.stats[0] = page_stats(0);
318 stats.stats[1] = page_stats(1);
319 stats.stats[2] = page_stats(2);
325 auto flushPages = [](MemoryPageContainer& container) {
326 for (
auto it = container.pagesFree.begin(); it != container.pagesFree.end();) {
327 auto* pPage = &(*it);
334 container.pagesFree.unlink(pPage);
339 for (
auto& c: m_pages)
345 auto diagPage = [](
const ChunkAllocatorPageStats& stats, uint32_t sizeType) {
346 GAIA_LOG_N(
"ChunkAllocator %uK stats", mem_block_size(sizeType) / 1024);
347 GAIA_LOG_N(
" Allocated: %" PRIu64
" B", stats.mem_total);
348 GAIA_LOG_N(
" Used: %" PRIu64
" B", stats.mem_total - stats.mem_used);
349 GAIA_LOG_N(
" Overhead: %" PRIu64
" B", stats.mem_used);
351 " Utilization: %.1f%%",
352 stats.mem_total ? 100.0 * ((
double)stats.mem_used / (
double)stats.mem_total) : 0);
353 GAIA_LOG_N(
" Pages: %u", stats.num_pages);
354 GAIA_LOG_N(
" Free pages: %u", stats.num_pages_free);
357 auto memStats = stats();
358 diagPage(memStats.stats[0], 0);
359 diagPage(memStats.stats[1], 1);
360 diagPage(memStats.stats[2], 2);
364 static constexpr const char* s_strChunkAlloc_Chunk =
"Chunk";
365 static constexpr const char* s_strChunkAlloc_MemPage =
"MemoryPage";
367 static MemoryPage* alloc_page(uint8_t sizeType) {
368 const uint32_t size = mem_block_size(sizeType) * MemoryPage::NBlocks;
369 auto* pPageData = mem::AllocHelper::alloc_alig<uint8_t>(s_strChunkAlloc_Chunk, 16U, size);
370 auto* pMemoryPage = mem::AllocHelper::alloc<MemoryPage>(s_strChunkAlloc_MemPage);
371 return new (pMemoryPage) MemoryPage(pPageData, sizeType);
374 static void free_page(MemoryPage* pMemoryPage) {
375 GAIA_ASSERT(pMemoryPage !=
nullptr);
377 mem::AllocHelper::free_alig(s_strChunkAlloc_Chunk, pMemoryPage->m_data);
378 pMemoryPage->~MemoryPage();
379 mem::AllocHelper::free(s_strChunkAlloc_MemPage, pMemoryPage);
386 void try_delete_this() {
388 bool allEmpty =
true;
389 for (
const auto& c: m_pages)
390 allEmpty = allEmpty && c.empty();
395 ChunkAllocatorPageStats page_stats(uint32_t sizeType)
const {
396 ChunkAllocatorPageStats stats{};
397 const auto& container = m_pages[sizeType];
399 stats.num_pages = (uint32_t)container.pagesFree.size() + (uint32_t)container.pagesFull.size();
400 stats.num_pages_free = (uint32_t)container.pagesFree.size();
401 stats.mem_total = stats.num_pages * (size_t)mem_block_size(sizeType) * MemoryPage::NBlocks;
402 stats.mem_used = container.pagesFull.size() * (size_t)mem_block_size(sizeType) * MemoryPage::NBlocks;
404 const auto& pagesFree = container.pagesFree;
405 for (
const auto& page: pagesFree)
406 stats.mem_used += page.used_blocks_cnt() * (size_t)MaxMemoryBlockSize;
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9