Last active
November 15, 2017 12:12
-
-
Save silverweed/77f021c7e844262ff57bdbc302375621 to your computer and use it in GitHub Desktop.
Pool allocator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <cstdlib> | |
#include <cstdio> | |
#include <cstdint> | |
#include <cassert> | |
#include <utility> | |
#define ABORT(x) do { \ | |
fprintf(stderr, "%s\n", x); \ | |
std::abort(); \ | |
} while (false) | |
/* A simple pool allocator type-safe class. | |
* Internally uses a contiguous memory block organized as a linked list. | |
* Alloc and dealloc operations are O(1). | |
* Capacity is decided on creation and cannot be changed afterwards. | |
* Requires explicit startup and shutdown. | |
*/ | |
template <class T> | |
class PoolAllocator { | |
public: | |
explicit PoolAllocator(size_t maxElems = 1024) : capacity(maxElems + 1) { | |
static_assert(sizeof(T) >= sizeof(uintptr_t), "sizeof(T) < sizeof(uintptr_t)!"); | |
} | |
PoolAllocator(const PoolAllocator&) = delete; | |
PoolAllocator(PoolAllocator&&) = delete; | |
void startup() { | |
if (active) | |
ABORT("PoolAllocator initialized twice!"); | |
pool = reinterpret_cast<T*>(malloc(capacity * sizeof(T))); | |
_fillPool(); | |
active = true; | |
} | |
void shutdown() { | |
if (!active) | |
ABORT("PoolAllocator shut down twice!"); | |
free(pool); | |
active = false; | |
} | |
T* alloc() { | |
if (!active) | |
ABORT("Tried to allocate memory on uninitialized allocator!"); | |
const auto firstFreeAddr = *reinterpret_cast<uintptr_t*>(pool); | |
if (firstFreeAddr == 0) | |
ABORT("PoolAllocator: Out of memory"); | |
const auto next = *reinterpret_cast<uintptr_t*>(firstFreeAddr); | |
*reinterpret_cast<uintptr_t*>(pool) = reinterpret_cast<uintptr_t>(next); | |
return reinterpret_cast<T*>(firstFreeAddr); | |
} | |
void dealloc(T *mem) { | |
if (!active) { | |
// Silently fail, memory was already freed | |
return; | |
} | |
assert(mem > pool && mem < pool + capacity + sizeof(T) | |
&& "Tried to deallocate memory not belonging to the pool!"); | |
const auto firstFreeAddr = *reinterpret_cast<uintptr_t*>(pool); | |
*reinterpret_cast<uintptr_t*>(mem) = firstFreeAddr; | |
*reinterpret_cast<uintptr_t*>(pool) = reinterpret_cast<uintptr_t>(mem); | |
} | |
template <class... Args> | |
T* create(Args&&... args) { | |
T *mem = alloc(); | |
return new (mem) T(std::forward<Args>(args)...); | |
} | |
void destroy(T *obj) { | |
obj->~T(); | |
dealloc(obj); | |
} | |
#ifdef DEBUG | |
void dump() const { | |
for (size_t i = 0; i < capacity; ++i) { | |
printf("(%p): %p | ", pool + i, *(reinterpret_cast<uintptr_t*>(pool + i))); | |
} | |
printf("\n"); | |
} | |
/** Calculates the remaining capacity of the pool "the hard way". Used for debugging */ | |
size_t realRemainingCapacity() const { | |
size_t c = 0; | |
auto nxt = *reinterpret_cast<uintptr_t*>(pool); | |
while (nxt != 0) { | |
++c; | |
nxt = *reinterpret_cast<uintptr_t*>(nxt); | |
} | |
return c; | |
} | |
size_t totMem() const { return capacity * sizeof(T); } | |
#endif | |
private: | |
T *pool; | |
size_t capacity; | |
bool active = false; | |
/** Fills the pool with a linked list of pointers */ | |
void _fillPool() { | |
for (size_t i = 0; i < capacity - 1; ++i) { | |
const auto curAddr = pool + i; | |
*reinterpret_cast<uintptr_t*>(curAddr) = reinterpret_cast<uintptr_t>(curAddr + 1); | |
} | |
*reinterpret_cast<uintptr_t*>(pool + capacity - 1) = 0x0; | |
} | |
}; | |
#undef ABORT |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment