87 static constexpr uint16_t NBlocks = 64;
88 static constexpr uint16_t NBlocks_Bits = (uint16_t)core::count_bits(NBlocks);
89 static constexpr uint16_t SizeTypeBits = (uint16_t)core::count_bits(SmallBlockSizeTypeCount - 1);
90 static constexpr uint32_t InvalidBlockId = NBlocks + 1;
92 static constexpr uint8_t FreedBlockPattern = 0xDD;
94 static constexpr uint32_t BlockArrayBytes = ((uint32_t)NBlocks_Bits * (uint32_t)NBlocks + 7) / 8;
102 uint32_t m_sizeType : SizeTypeBits;
103 uint32_t m_blockCnt : NBlocks_Bits;
104 uint32_t m_usedBlocks : NBlocks_Bits;
105 uint32_t m_nextFreeBlock : NBlocks_Bits;
106 uint32_t m_freeBlocks : NBlocks_Bits;
108#if GAIA_ASSERT_ENABLED
109 uint64_t m_usedMask = 0;
116 m_data(ptr), m_sizeType(sizeType), m_blockCnt(0), m_usedBlocks(0), m_nextFreeBlock(0), m_freeBlocks(0) {}
120 return small_block_stride(m_sizeType);
123 void write_block_idx(uint32_t blockIdx, uint32_t value) {
124 const uint32_t bitPosition = blockIdx * NBlocks_Bits;
126 GAIA_ASSERT(bitPosition < NBlocks * NBlocks_Bits);
127 GAIA_ASSERT(value <= InvalidBlockId);
129 BitView{{(uint8_t*)m_blocks.data(), BlockArrayBytes}}.set(bitPosition, (uint8_t)value);
132 GAIA_NODISCARD uint8_t read_block_idx(uint32_t blockIdx)
const {
133 const uint32_t bitPosition = blockIdx * NBlocks_Bits;
135 GAIA_ASSERT(bitPosition < NBlocks * NBlocks_Bits);
137 return BitView{{(uint8_t*)m_blocks.data(), BlockArrayBytes}}.get(bitPosition);
144 GAIA_NODISCARD
void*
alloc_block(uint32_t bytesWanted){
150 auto store_block_address = [&](uint32_t index) {
151 auto* pMemoryBlock = (uint8_t*)m_data + (index *
block_stride());
152 GAIA_ASSERT((uintptr_t)pMemoryBlock % SmallBlockAlignment == 0);
153 auto& header = block_header(pMemoryBlock);
154 header.m_pageAddr = (uintptr_t)
this;
156 header.m_requestedBytes = bytesWanted;
157 header.m_reserved = 0;
159 header.m_reserved = 0;
161 auto* pData = pMemoryBlock + SmallBlockUsableOffset;
162 GAIA_ASSERT((uintptr_t)pData % SmallBlockAlignment == 0);
166 GAIA_ASSERT(!
full() &&
"Trying to allocate too many blocks!");
169 if (m_freeBlocks == 0U) {
173 write_block_idx(index, index);
175 GAIA_ASSERT(m_nextFreeBlock < m_blockCnt &&
"Block allocator recycle list broken!");
180 index = m_nextFreeBlock;
181 m_nextFreeBlock = read_block_idx(m_nextFreeBlock);
184#if GAIA_ASSERT_ENABLED
185 GAIA_ASSERT((m_usedMask & (uint64_t(1) << index)) == 0 &&
"Block already marked as live");
186 m_usedMask |= uint64_t(1) << index;
189 return store_block_address(index);
195 GAIA_ASSERT(pBlock !=
nullptr);
196 GAIA_ASSERT(m_usedBlocks > 0);
197 GAIA_ASSERT(m_freeBlocks <= NBlocks);
199 const auto* pMemoryBlock = (uint8_t*)pBlock - SmallBlockUsableOffset;
200 const auto blckAddr = (uintptr_t)pMemoryBlock;
201 GAIA_ASSERT(blckAddr % SmallBlockAlignment == 0);
202 const auto dataAddr = (uintptr_t)m_data;
203 GAIA_ASSERT(blckAddr >= dataAddr);
205#if GAIA_ASSERT_ENABLED
206 const auto pageSize = blockStride * NBlocks;
207 GAIA_ASSERT(blckAddr < dataAddr + pageSize);
209 GAIA_ASSERT((blckAddr - dataAddr) % blockStride == 0);
210 const auto blockIdx = (uint32_t)((blckAddr - dataAddr) / blockStride);
211 GAIA_ASSERT(blockIdx < m_blockCnt);
214 auto& header = block_header((
void*)pMemoryBlock);
215 GAIA_ASSERT(header.m_requestedBytes > 0);
217#if GAIA_ASSERT_ENABLED
218 GAIA_ASSERT((m_usedMask & (uint64_t(1) << blockIdx)) != 0 &&
"Double free or corrupted block state");
219 m_usedMask &= ~(uint64_t(1) << blockIdx);
223 header.m_requestedBytes = 0;
224 std::memset(pBlock, FreedBlockPattern, small_block_size(m_sizeType));
227 if (m_freeBlocks == 0U)
228 write_block_idx(blockIdx, InvalidBlockId);
230 write_block_idx(blockIdx, m_nextFreeBlock);
231 m_nextFreeBlock = blockIdx;
243 GAIA_NODISCARD
bool full()
const {
254#if GAIA_ASSERT_ENABLED
255 GAIA_ASSERT(m_sizeType < SmallBlockSizeTypeCount);
256 GAIA_ASSERT(m_blockCnt <= NBlocks);
257 GAIA_ASSERT(m_usedBlocks <= m_blockCnt);
258 GAIA_ASSERT(m_freeBlocks <= m_blockCnt);
259 GAIA_ASSERT(m_usedBlocks + m_freeBlocks == m_blockCnt);
260 GAIA_ASSERT(((uintptr_t)m_data % SmallBlockAlignment) == 0);
262 [[maybe_unused]] uint64_t freeMask = 0;
264 if (m_freeBlocks != 0) {
265 uint32_t next = m_nextFreeBlock;
266 GAIA_FOR(m_freeBlocks) {
267 GAIA_ASSERT(next < m_blockCnt);
269 const auto bit = uint64_t(1) << next;
270 GAIA_ASSERT((freeMask & bit) == 0 &&
"Free list contains a cycle");
273 next = read_block_idx(next);
276 GAIA_ASSERT(next == InvalidBlockId);
279 GAIA_FOR(m_blockCnt) {
280 const auto* pMemoryBlock = (
const uint8_t*)m_data + (i *
block_stride());
281 const auto& header = block_header(pMemoryBlock);
282 GAIA_ASSERT(header.m_pageAddr == (uintptr_t)
this);
283 GAIA_ASSERT(((uintptr_t)pMemoryBlock % SmallBlockAlignment) == 0);
286 const bool isFree = (freeMask & (uint64_t(1) << i)) != 0;
287 GAIA_ASSERT((header.m_requestedBytes == 0) == isFree);
292 GAIA_ASSERT((m_usedMask & freeMask) == 0);
293 const auto liveMask = m_blockCnt == 64 ? ~uint64_t(0) : ((uint64_t(1) << m_blockCnt) - 1);
294 GAIA_ASSERT((m_usedMask | freeMask) == liveMask);
301 GAIA_NODISCARD uint64_t requested_bytes()
const {
302 if (m_usedBlocks == 0)
305 uint64_t freeMask = 0;
306 uint32_t next = m_nextFreeBlock;
307 GAIA_FOR(m_freeBlocks) {
308 GAIA_ASSERT(next < m_blockCnt);
309 const auto bit = uint64_t(1) << next;
310 GAIA_ASSERT((freeMask & bit) == 0 &&
"Free list contains a cycle");
312 next = read_block_idx(next);
315 uint64_t requested = 0;
316 GAIA_FOR(m_blockCnt) {
317 if ((freeMask & (uint64_t(1) << i)) != 0)
320 const auto* pMemoryBlock = (
const uint8_t*)m_data + (i *
block_stride());
321 requested += block_header(pMemoryBlock).m_requestedBytes;
329 static SmallBlockHeader& block_header(
void* pMemoryBlock) {
330 return *(SmallBlockHeader*)pMemoryBlock;
333 static const SmallBlockHeader& block_header(
const void* pMemoryBlock) {
334 return *(
const SmallBlockHeader*)pMemoryBlock;
352 friend ::gaia::mem::SmallBlockAllocator;
355 bool m_isDone =
false;
360 static constexpr uint32_t MAX_SIZE = SmallBlockMaxSize;
365#if GAIA_ASSERT_ENABLED
366 for (
const auto& container: m_pages) {
367 const bool hasPages = container.pagesEmpty.first !=
nullptr || container.pagesPartial.first !=
nullptr ||
368 container.pagesFull.first !=
nullptr;
369 GAIA_ASSERT(!hasPages &&
"SmallBlockAllocator leaking memory");
382 GAIA_NODISCARD
void*
alloc(uint32_t bytesWanted) {
383 GAIA_ASSERT(bytesWanted > 0);
384 GAIA_ASSERT(bytesWanted <= MAX_SIZE);
385 if (bytesWanted == 0 || bytesWanted > MAX_SIZE)
388 const auto sizeType = small_block_size_type(bytesWanted);
389 auto& container = m_pages[sizeType];
391 SmallBlockPageState prevState = SmallBlockPageState::Partial;
392 auto* pPage = container.pagesPartial.first;
393 if (pPage ==
nullptr) {
394 prevState = SmallBlockPageState::Empty;
395 pPage = container.pagesEmpty.first;
396 if (pPage ==
nullptr) {
397 prevState = SmallBlockPageState::Detached;
398 pPage = alloc_page(sizeType);
403 void* pBlock = pPage->alloc_block(bytesWanted);
405 void* pBlock = pPage->alloc_block();
407 GAIA_PROF_ALLOC(pBlock, bytesWanted);
408 move_page(container, pPage, prevState, state_for(*pPage));
416 GAIA_ASSERT(pBlock !=
nullptr);
417 if (pBlock ==
nullptr)
420 const auto& header = *(
const SmallBlockHeader*)((uint8_t*)pBlock - SmallBlockUsableOffset);
421 const auto pageAddr = header.m_pageAddr;
422 GAIA_ASSERT(pageAddr %
sizeof(uintptr_t) == 0);
424 GAIA_ASSERT(header.m_requestedBytes > 0);
427 const auto prevState = state_for(*pPage);
428 auto& container = m_pages[pPage->m_sizeType];
430 GAIA_PROF_FREE(pBlock);
431 pPage->free_block(pBlock);
432 move_page(container, pPage, prevState, state_for(*pPage));
436 if (pPage->empty()) {
437 container.pagesEmpty.unlink(pPage);
447 void flush(
bool releaseAll =
false) {
448 for (uint32_t i = 0; i < SmallBlockSizeTypeCount; ++i)
449 flush_pages(m_pages[i], releaseAll);
456 for (uint32_t sizeType = 0; sizeType < SmallBlockSizeTypeCount; ++sizeType)
457 stats.stats[sizeType] = page_stats(sizeType);
463 const auto allStats = stats();
464 for (uint32_t sizeType = 0; sizeType < SmallBlockSizeTypeCount; ++sizeType) {
465 const auto& stats = allStats.stats[sizeType];
466 if (stats.num_pages == 0)
469 GAIA_LOG_N(
"SmallBlockAllocator %u B stats", small_block_size(sizeType));
470 GAIA_LOG_N(
" Allocated: %" PRIu64
" B", stats.mem_total);
471 GAIA_LOG_N(
" Reserved by live blocks: %" PRIu64
" B", stats.mem_used);
472 GAIA_LOG_N(
" Pages: %u", stats.num_pages);
473 GAIA_LOG_N(
" Reusable pages: %u", stats.num_pages_free);
476 " Utilization: %.1f%%",
477 stats.mem_total ? 100.0 * ((
double)stats.mem_used / (
double)stats.mem_total) : 0.0);
479 GAIA_LOG_N(
" Requested: %" PRIu64
" B", stats.mem_requested);
480 GAIA_LOG_N(
" Free capacity: %" PRIu64
" B", stats.mem_total - stats.mem_used);
481 GAIA_LOG_N(
" Internal slack: %" PRIu64
" B", stats.mem_used - stats.mem_requested);
483 " Utilization: %.1f%%",
484 stats.mem_total ? 100.0 * ((
double)stats.mem_requested / (
double)stats.mem_total) : 0.0);
485 GAIA_LOG_N(
" Empty pages: %u", stats.num_pages_empty);
492#if GAIA_ASSERT_ENABLED
493 for (uint32_t sizeType = 0; sizeType < SmallBlockSizeTypeCount; ++sizeType)
494 verify_container(m_pages[sizeType], sizeType);
499 static constexpr const char* s_strSmallBlockData =
"SmallBlockData";
500 static constexpr const char* s_strSmallBlockPage =
"SmallBlockPage";
503 const uint32_t size = small_block_stride(sizeType) * SmallBlockPage::NBlocks;
504 auto* pPageData = AllocHelper::alloc_alig<uint8_t>(s_strSmallBlockData, SmallBlockAlignment, size);
505 auto* pMemoryPage = AllocHelper::alloc<SmallBlockPage>(s_strSmallBlockPage);
509 static void free_page(SmallBlockPage* pPage) {
510 GAIA_ASSERT(pPage !=
nullptr);
512 AllocHelper::free_alig(s_strSmallBlockData, pPage->m_data);
513 pPage->~SmallBlockPage();
514 AllocHelper::free(s_strSmallBlockPage, pPage);
521 void try_delete_this() {
522 bool allEmpty =
true;
523 for (
const auto& container: m_pages) {
524 const bool hasPages = container.pagesEmpty.first !=
nullptr || container.pagesPartial.first !=
nullptr ||
525 container.pagesFull.first !=
nullptr;
526 allEmpty = allEmpty && !hasPages;
533 static constexpr uint32_t warm_pages_to_keep() {
537 static SmallBlockPageState state_for(
const SmallBlockPage& page) {
539 return SmallBlockPageState::Empty;
541 return SmallBlockPageState::Full;
542 return SmallBlockPageState::Partial;
545 static cnt::fwd_llist<SmallBlockPage>& page_list(SmallBlockPageContainer& container, SmallBlockPageState state) {
547 case SmallBlockPageState::Empty:
548 return container.pagesEmpty;
549 case SmallBlockPageState::Partial:
550 return container.pagesPartial;
552 GAIA_ASSERT(state == SmallBlockPageState::Full);
553 return container.pagesFull;
557 static void move_page(
558 SmallBlockPageContainer& container, SmallBlockPage* pPage, SmallBlockPageState fromState,
559 SmallBlockPageState toState) {
560 if (fromState == toState)
563 if (fromState != SmallBlockPageState::Detached)
564 page_list(container, fromState).unlink(pPage);
565 page_list(container, toState).link(pPage);
568 [[maybe_unused]]
static void verify_page_membership(
569 [[maybe_unused]]
const SmallBlockPage& page,
570 [[maybe_unused]] uint32_t sizeType,
571 [[maybe_unused]] SmallBlockPageState expectedState
573 GAIA_ASSERT(page.m_sizeType == sizeType);
574 GAIA_ASSERT(state_for(page) == expectedState);
575 GAIA_ASSERT(page.get_fwd_llist_link().linked());
578 static void verify_container(
const SmallBlockPageContainer& container, uint32_t sizeType) {
579 for (
const auto& page: container.pagesEmpty) {
580 verify_page_membership(page, sizeType, SmallBlockPageState::Empty);
584 for (
const auto& page: container.pagesPartial) {
585 verify_page_membership(page, sizeType, SmallBlockPageState::Partial);
589 for (
const auto& page: container.pagesFull) {
590 verify_page_membership(page, sizeType, SmallBlockPageState::Full);
595 GAIA_NODISCARD SmallBlockAllocatorPageStats page_stats(uint32_t sizeType)
const {
596 SmallBlockAllocatorPageStats stats{};
597 const auto& container = m_pages[sizeType];
598 const auto blockStride = (uint64_t)small_block_stride(sizeType);
599 const auto pageSize = blockStride * SmallBlockPage::NBlocks;
601 stats.num_pages = (uint32_t)container.pagesEmpty.size() + (uint32_t)container.pagesPartial.size() +
602 (uint32_t)container.pagesFull.size();
603 stats.num_pages_free = (uint32_t)container.pagesEmpty.size() + (uint32_t)container.pagesPartial.size();
604 stats.mem_total = stats.num_pages * pageSize;
605 stats.mem_used = container.pagesFull.size() * pageSize;
608 stats.num_pages_empty = (uint32_t)container.pagesEmpty.size();
610 for (
const auto& page: container.pagesFull)
611 stats.mem_requested += page.requested_bytes();
613 for (
const auto& page: container.pagesPartial) {
614 stats.mem_used += page.used_blocks_cnt() * blockStride;
615 stats.mem_requested += page.requested_bytes();
618 for (
const auto& page: container.pagesPartial)
619 stats.mem_used += page.used_blocks_cnt() * blockStride;
625 static void flush_pages(SmallBlockPageContainer& container,
bool releaseAll) {
626 const bool keepWarmPage = !releaseAll && warm_pages_to_keep() != 0;
627 bool keptWarmPage =
false;
629 for (
auto it = container.pagesEmpty.begin(); it != container.pagesEmpty.end();) {
630 auto* pPage = &(*it);
636 if (keepWarmPage && !keptWarmPage) {
641 container.pagesEmpty.unlink(pPage);
uint32_t num_pages_free
Number of reusable pages (partial + empty).
Definition smallblock_allocator.h:64
uint64_t mem_used
Memory reserved by live blocks for this size class.
Definition smallblock_allocator.h:60
uint32_t num_pages
Number of allocated pages.
Definition smallblock_allocator.h:62
uint64_t mem_total
Total allocated memory for this size class.
Definition smallblock_allocator.h:58
GAIA_NODISCARD uint32_t block_stride() const
Returns the stride of one block in this page.
Definition smallblock_allocator.h:119
GAIA_NODISCARD void * alloc_block()
Allocates one block from this page.
Definition smallblock_allocator.h:148
GAIA_NODISCARD bool empty() const
Returns true when the page is empty.
Definition smallblock_allocator.h:248
void free_block(void *pBlock)
Frees one block back to this page.
Definition smallblock_allocator.h:194
void verify() const
Verifies internal page invariants.
Definition smallblock_allocator.h:253
SmallBlockPage(void *ptr, uint8_t sizeType)
Constructs a page for the given size class.
Definition smallblock_allocator.h:115
GAIA_NODISCARD bool full() const
Returns true when the page is full.
Definition smallblock_allocator.h:243
GAIA_NODISCARD uint32_t used_blocks_cnt() const
Returns the number of live blocks.
Definition smallblock_allocator.h:238