/********************************************************************************************** * * rmem v1.3 - raylib memory pool and objects pool * * A quick, efficient, and minimal free list and arena-based allocator * * PURPOSE: * - A quicker, efficient memory allocator alternative to 'malloc()' and friends. * - Reduce the possibilities of memory leaks for beginner developers using raylib. * - Being able to flexibly range check memory if necessary. * * CONFIGURATION: * * #define RMEM_IMPLEMENTATION * Generates the implementation of the library into the included file. * If not defined, the library is in header only mode and can be included in other headers * or source files without problems. But only ONE file should hold the implementation. * * DOCUMENTATION: * * raylib Wiki: https://github.com/raysan5/raylib/wiki/raylib-memory-pool * Usage example with raylib: https://github.com/raysan5/raylib/issues/1329 * * CHANGELOG: * * v1.0: First version * v1.1: Bug patches for the mempool and addition of object pool * v1.2: Addition of bidirectional arena * v1.3: Several changes: * Pptimizations of allocators * Renamed 'Stack' to 'Arena' * Replaced certain define constants with an anonymous enum * Refactored MemPool to no longer require active or deferred defragging * * * LICENSE: zlib/libpng * * Copyright (c) 2019 Kevin 'Assyrianic' Yonan (@assyrianic) and reviewed by Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, including commercial * applications, and to alter it and redistribute it freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not claim that you * wrote the original software. If you use this software in a product, an acknowledgment * in the product documentation would be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be misrepresented * as being the original software. * * 3. This notice may not be removed or altered from any source distribution. * **********************************************************************************************/ #ifndef RMEM_H #define RMEM_H #include #include //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) #define RMEMAPI __declspec(dllexport) // We are building library as a Win32 shared library (.dll) #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) #define RMEMAPI __declspec(dllimport) // We are using library as a Win32 shared library (.dll) #else #define RMEMAPI // We are building or using library as a static library (or Linux shared library) #endif #define RMEM_VERSION "v1.3" // changelog at bottom of header. //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- enum { MEMPOOL_BUCKET_SIZE = 8, MEMPOOL_BUCKET_BITS = (sizeof(uintptr_t) >> 1) + 1, MEM_SPLIT_THRESHOLD = sizeof(uintptr_t) * 4 }; // Memory pool node typedef struct MemNode MemNode; struct MemNode { size_t size; MemNode *next, *prev; }; // Freelist implementation typedef struct AllocList { MemNode *head, *tail; size_t len; } AllocList; // Arena allocator typedef struct Arena { uintptr_t mem, offs; size_t size; } Arena; // Memory pool typedef struct MemPool { AllocList large, buckets[MEMPOOL_BUCKET_SIZE]; Arena arena; } MemPool; // Object pool typedef struct ObjPool { uintptr_t mem, offs; size_t objSize, freeBlocks, memSize; } ObjPool; // Double-ended stack (aka Deque) typedef struct BiStack { uintptr_t mem, front, back; size_t size; } BiStack; #if defined(__cplusplus) extern "C" { // Prevents name mangling of functions #endif //------------------------------------------------------------------------------------ // Functions Declaration - Memory Pool //------------------------------------------------------------------------------------ RMEMAPI MemPool CreateMemPool(size_t bytes); RMEMAPI MemPool CreateMemPoolFromBuffer(void *buf, size_t bytes); RMEMAPI void DestroyMemPool(MemPool *mempool); RMEMAPI void *MemPoolAlloc(MemPool *mempool, size_t bytes); RMEMAPI void *MemPoolRealloc(MemPool *mempool, void *ptr, size_t bytes); RMEMAPI void MemPoolFree(MemPool *mempool, void *ptr); RMEMAPI void MemPoolCleanUp(MemPool *mempool, void **ptrref); RMEMAPI void MemPoolReset(MemPool *mempool); RMEMAPI size_t GetMemPoolFreeMemory(const MemPool mempool); //------------------------------------------------------------------------------------ // Functions Declaration - Object Pool //------------------------------------------------------------------------------------ RMEMAPI ObjPool CreateObjPool(size_t objsize, size_t len); RMEMAPI ObjPool CreateObjPoolFromBuffer(void *buf, size_t objsize, size_t len); RMEMAPI void DestroyObjPool(ObjPool *objpool); RMEMAPI void *ObjPoolAlloc(ObjPool *objpool); RMEMAPI void ObjPoolFree(ObjPool *objpool, void *ptr); RMEMAPI void ObjPoolCleanUp(ObjPool *objpool, void **ptrref); //------------------------------------------------------------------------------------ // Functions Declaration - Double-Ended Stack //------------------------------------------------------------------------------------ RMEMAPI BiStack CreateBiStack(size_t len); RMEMAPI BiStack CreateBiStackFromBuffer(void *buf, size_t len); RMEMAPI void DestroyBiStack(BiStack *destack); RMEMAPI void *BiStackAllocFront(BiStack *destack, size_t len); RMEMAPI void *BiStackAllocBack(BiStack *destack, size_t len); RMEMAPI void BiStackResetFront(BiStack *destack); RMEMAPI void BiStackResetBack(BiStack *destack); RMEMAPI void BiStackResetAll(BiStack *destack); RMEMAPI intptr_t BiStackMargins(BiStack destack); #ifdef __cplusplus } #endif #endif // RMEM_H /*********************************************************************************** * * RMEM IMPLEMENTATION * ************************************************************************************/ #if defined(RMEM_IMPLEMENTATION) #include // Required for: malloc(), calloc(), free() #include // Required for: memset(), memcpy(), memmove() //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- // Make sure restrict type qualifier for pointers is defined // NOTE: Not supported by C++, it is a C only keyword #if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) || defined(_MSC_VER) #ifndef restrict #define restrict __restrict #endif #endif //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- // ... //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- static inline size_t __AlignSize(const size_t size, const size_t align) { return (size + (align - 1)) & -align; } static MemNode *__SplitMemNode(MemNode *const node, const size_t bytes) { uintptr_t n = ( uintptr_t )node; MemNode *const r = ( MemNode* )(n + (node->size - bytes)); node->size -= bytes; r->size = bytes; return r; } static void __InsertMemNodeBefore(AllocList *const list, MemNode *const insert, MemNode *const curr) { insert->next = curr; if (curr->prev==NULL) list->head = insert; else { insert->prev = curr->prev; curr->prev->next = insert; } curr->prev = insert; } static void __ReplaceMemNode(MemNode *const old, MemNode *const replace) { replace->prev = old->prev; replace->next = old->next; if (old->prev != NULL) old->prev->next = replace; if (old->next != NULL) old->next->prev = replace; } static MemNode *__RemoveMemNode(AllocList *const list, MemNode *const node) { if (node->prev != NULL) node->prev->next = node->next; else { list->head = node->next; if (list->head != NULL) list->head->prev = NULL; else list->tail = NULL; } if (node->next != NULL) node->next->prev = node->prev; else { list->tail = node->prev; if (list->tail != NULL) list->tail->next = NULL; else list->head = NULL; } list->len--; return node; } static MemNode *__FindMemNode(AllocList *const list, const size_t bytes) { for (MemNode *node = list->head; node != NULL; node = node->next) { if (node->size < bytes) continue; // Close in size - reduce fragmentation by not splitting else if (node->size <= bytes + MEM_SPLIT_THRESHOLD) return __RemoveMemNode(list, node); else return __SplitMemNode(node, bytes); } return NULL; } static void __InsertMemNode(MemPool *const mempool, AllocList *const list, MemNode *const node, const bool is_bucket) { if (list->head == NULL) { list->head = node; list->len++; } else { for (MemNode *iter = list->head; iter != NULL; iter = iter->next) { if ((uintptr_t)iter == mempool->arena.offs) { mempool->arena.offs += iter->size; __RemoveMemNode(list, iter); iter = list->head; if (iter == NULL) { list->head = node; return; } } const uintptr_t inode = (uintptr_t)node; const uintptr_t iiter = (uintptr_t)iter; const uintptr_t iter_end = iiter + iter->size; const uintptr_t node_end = inode + node->size; if (iter == node) return; else if (iter < node) { // node was coalesced prior. if (iter_end > inode) return; else if ((iter_end == inode) && !is_bucket) { // if we can coalesce, do so. iter->size += node->size; return; } else if (iter->next == NULL) { // we reached the end of the free list -> append the node iter->next = node; node->prev = iter; list->len++; return; } } else if (iter > node) { // Address sort, lowest to highest aka ascending order. if (iiter < node_end) return; else if ((iter == list->head) && !is_bucket) { if (iter_end == inode) iter->size += node->size; else if (node_end == iiter) { node->size += list->head->size; node->next = list->head->next; node->prev = NULL; list->head = node; } else { node->next = iter; node->prev = NULL; iter->prev = node; list->head = node; list->len++; } return; } else if ((iter_end == inode) && !is_bucket) { // if we can coalesce, do so. iter->size += node->size; return; } else { __InsertMemNodeBefore(list, node, iter); list->len++; return; } } } } } //---------------------------------------------------------------------------------- // Module Functions Definition - Memory Pool //---------------------------------------------------------------------------------- MemPool CreateMemPool(const size_t size) { MemPool mempool = { 0 }; if (size == 0) return mempool; else { // Align the mempool size to at least the size of an alloc node. uint8_t *const restrict buf = malloc(size*sizeof *buf); if (buf==NULL) return mempool; else { mempool.arena.size = size; mempool.arena.mem = (uintptr_t)buf; mempool.arena.offs = mempool.arena.mem + mempool.arena.size; return mempool; } } } MemPool CreateMemPoolFromBuffer(void *const restrict buf, const size_t size) { MemPool mempool = { 0 }; if ((size == 0) || (buf == NULL) || (size <= sizeof(MemNode))) return mempool; else { mempool.arena.size = size; mempool.arena.mem = (uintptr_t)buf; mempool.arena.offs = mempool.arena.mem + mempool.arena.size; return mempool; } } void DestroyMemPool(MemPool *const restrict mempool) { if (mempool->arena.mem == 0) return; else { void *const restrict ptr = (void *)mempool->arena.mem; free(ptr); *mempool = (MemPool){ 0 }; } } void *MemPoolAlloc(MemPool *const mempool, const size_t size) { if ((size == 0) || (size > mempool->arena.size)) return NULL; else { MemNode *new_mem = NULL; const size_t ALLOC_SIZE = __AlignSize(size + sizeof *new_mem, sizeof(intptr_t)); const size_t BUCKET_SLOT = (ALLOC_SIZE >> MEMPOOL_BUCKET_BITS) - 1; // If the size is small enough, let's check if our buckets has a fitting memory block. if (BUCKET_SLOT < MEMPOOL_BUCKET_SIZE) { new_mem = __FindMemNode(&mempool->buckets[BUCKET_SLOT], ALLOC_SIZE); } else if (mempool->large.head != NULL) { new_mem = __FindMemNode(&mempool->large, ALLOC_SIZE); } if (new_mem == NULL) { // not enough memory to support the size! if ((mempool->arena.offs - ALLOC_SIZE) < mempool->arena.mem) return NULL; else { // Couldn't allocate from a freelist, allocate from available mempool. // Subtract allocation size from the mempool. mempool->arena.offs -= ALLOC_SIZE; // Use the available mempool space as the new node. new_mem = ( MemNode* )mempool->arena.offs; new_mem->size = ALLOC_SIZE; } } // Visual of the allocation block. // -------------- // | mem size | lowest addr of block // | next node | 12 byte (32-bit) header // | prev node | 24 byte (64-bit) header // |------------| // | alloc'd | // | memory | // | space | highest addr of block // -------------- new_mem->next = new_mem->prev = NULL; uint8_t *const restrict final_mem = (uint8_t *)new_mem + sizeof *new_mem; return memset(final_mem, 0, new_mem->size - sizeof *new_mem); } } void *MemPoolRealloc(MemPool *const restrict mempool, void *const ptr, const size_t size) { if (size > mempool->arena.size) return NULL; // NULL ptr should make this work like regular Allocation else if (ptr == NULL) return MemPoolAlloc(mempool, size); else if ((uintptr_t)ptr - sizeof(MemNode) < mempool->arena.mem) return NULL; else { MemNode *const node = (MemNode *)((uint8_t *)ptr - sizeof *node); const size_t NODE_SIZE = sizeof *node; uint8_t *const resized_block = MemPoolAlloc(mempool, size); if (resized_block == NULL) return NULL; else { MemNode *const resized = (MemNode *)(resized_block - sizeof *resized); memmove(resized_block, ptr, (node->size > resized->size)? (resized->size - NODE_SIZE) : (node->size - NODE_SIZE)); MemPoolFree(mempool, ptr); return resized_block; } } } void MemPoolFree(MemPool *const restrict mempool, void *const ptr) { const uintptr_t p = (uintptr_t)ptr; if ((ptr == NULL) || (p - sizeof(MemNode) < mempool->arena.mem)) return; else { // Behind the actual pointer data is the allocation info. const uintptr_t block = p - sizeof(MemNode); MemNode *const mem_node = ( MemNode* )block; const size_t BUCKET_SLOT = (mem_node->size >> MEMPOOL_BUCKET_BITS) - 1; // Make sure the pointer data is valid. if ((block < mempool->arena.offs) || ((block - mempool->arena.mem) > mempool->arena.size) || (mem_node->size == 0) || (mem_node->size > mempool->arena.size)) return; // If the mem_node is right at the arena offs, then merge it back to the arena. else if (block == mempool->arena.offs) { mempool->arena.offs += mem_node->size; } else { // try to place it into bucket or large freelist. struct AllocList *const l = (BUCKET_SLOT < MEMPOOL_BUCKET_SIZE) ? &mempool->buckets[BUCKET_SLOT] : &mempool->large; __InsertMemNode(mempool, l, mem_node, (BUCKET_SLOT < MEMPOOL_BUCKET_SIZE)); } } } void MemPoolCleanUp(MemPool *const restrict mempool, void **const ptrref) { if ((ptrref == NULL) || (*ptrref == NULL)) return; else { MemPoolFree(mempool, *ptrref); *ptrref = NULL; } } size_t GetMemPoolFreeMemory(const MemPool mempool) { size_t total_remaining = mempool.arena.offs - mempool.arena.mem; for (MemNode *n = mempool.large.head; n != NULL; n = n->next) total_remaining += n->size; for (size_t i = 0; i < MEMPOOL_BUCKET_SIZE; i++) for (MemNode *n = mempool.buckets[i].head; n != NULL; n = n->next) total_remaining += n->size; return total_remaining; } void MemPoolReset(MemPool *const mempool) { mempool->large.head = mempool->large.tail = NULL; mempool->large.len = 0; for (size_t i = 0; i < MEMPOOL_BUCKET_SIZE; i++) { mempool->buckets[i].head = mempool->buckets[i].tail = NULL; mempool->buckets[i].len = 0; } mempool->arena.offs = mempool->arena.mem + mempool->arena.size; } //---------------------------------------------------------------------------------- // Module Functions Definition - Object Pool //---------------------------------------------------------------------------------- ObjPool CreateObjPool(const size_t objsize, const size_t len) { ObjPool objpool = { 0 }; if ((len == 0) || (objsize == 0)) return objpool; else { const size_t aligned_size = __AlignSize(objsize, sizeof(size_t)); uint8_t *const restrict buf = calloc(len, aligned_size); if (buf == NULL) return objpool; objpool.objSize = aligned_size; objpool.memSize = objpool.freeBlocks = len; objpool.mem = (uintptr_t)buf; for (size_t i=0; imem == 0) return; else { void *const restrict ptr = (void *)objpool->mem; free(ptr); *objpool = (ObjPool){ 0 }; } } void *ObjPoolAlloc(ObjPool *const objpool) { if (objpool->freeBlocks > 0) { // For first allocation, head points to the very first index. // Head = &pool[0]; // ret = Head == ret = &pool[0]; size_t *const restrict block = (size_t *)objpool->offs; objpool->freeBlocks--; // After allocating, we set head to the address of the index that *Head holds. // Head = &pool[*Head * pool.objsize]; objpool->offs = (objpool->freeBlocks != 0)? objpool->mem + (*block*objpool->objSize) : 0; return memset(block, 0, objpool->objSize); } else return NULL; } void ObjPoolFree(ObjPool *const restrict objpool, void *const ptr) { uintptr_t block = (uintptr_t)ptr; if ((ptr == NULL) || (block < objpool->mem) || (block > objpool->mem + objpool->memSize*objpool->objSize)) return; else { // When we free our pointer, we recycle the pointer space to store the previous index and then we push it as our new head. // *p = index of Head in relation to the buffer; // Head = p; size_t *const restrict index = (size_t *)block; *index = (objpool->offs != 0)? (objpool->offs - objpool->mem)/objpool->objSize : objpool->memSize; objpool->offs = block; objpool->freeBlocks++; } } void ObjPoolCleanUp(ObjPool *const restrict objpool, void **const restrict ptrref) { if (ptrref == NULL) return; else { ObjPoolFree(objpool, *ptrref); *ptrref = NULL; } } //---------------------------------------------------------------------------------- // Module Functions Definition - Double-Ended Stack //---------------------------------------------------------------------------------- BiStack CreateBiStack(const size_t len) { BiStack destack = { 0 }; if (len == 0) return destack; uint8_t *const buf = malloc(len*sizeof *buf); if (buf == NULL) return destack; destack.size = len; destack.mem = (uintptr_t)buf; destack.front = destack.mem; destack.back = destack.mem + len; return destack; } BiStack CreateBiStackFromBuffer(void *const buf, const size_t len) { BiStack destack = { 0 }; if ((len == 0) || (buf == NULL)) return destack; else { destack.size = len; destack.mem = destack.front = (uintptr_t)buf; destack.back = destack.mem + len; return destack; } } void DestroyBiStack(BiStack *const restrict destack) { if (destack->mem == 0) return; else { uint8_t *const restrict buf = (uint8_t *)destack->mem; free(buf); *destack = (BiStack){ 0 }; } } void *BiStackAllocFront(BiStack *const restrict destack, const size_t len) { if (destack->mem == 0) return NULL; else { const size_t ALIGNED_LEN = __AlignSize(len, sizeof(uintptr_t)); // front end arena is too high! if (destack->front + ALIGNED_LEN >= destack->back) return NULL; else { uint8_t *const restrict ptr = (uint8_t *)destack->front; destack->front += ALIGNED_LEN; return ptr; } } } void *BiStackAllocBack(BiStack *const restrict destack, const size_t len) { if (destack->mem == 0) return NULL; else { const size_t ALIGNED_LEN = __AlignSize(len, sizeof(uintptr_t)); // back end arena is too low if (destack->back - ALIGNED_LEN <= destack->front) return NULL; else { destack->back -= ALIGNED_LEN; uint8_t *const restrict ptr = (uint8_t *)destack->back; return ptr; } } } void BiStackResetFront(BiStack *const destack) { if (destack->mem == 0) return; else destack->front = destack->mem; } void BiStackResetBack(BiStack *const destack) { if (destack->mem == 0) return; else destack->back = destack->mem + destack->size; } void BiStackResetAll(BiStack *const destack) { BiStackResetBack(destack); BiStackResetFront(destack); } inline intptr_t BiStackMargins(const BiStack destack) { return destack.back - destack.front; } #endif // RMEM_IMPLEMENTATION