Skip to content

Commit

Permalink
pool memory allocator
Browse files Browse the repository at this point in the history
  • Loading branch information
malytomas committed Jan 5, 2024
1 parent 39df602 commit c69dd90
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 29 deletions.
2 changes: 1 addition & 1 deletion sources/include/cage-core/entities.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ namespace cage
EntityManager *manager() const;
uint32 name() const;

void add(EntityComponent *component);
CAGE_FORCE_INLINE void add(EntityComponent *component) { unsafeValue(component); }
template<ComponentConcept T>
CAGE_FORCE_INLINE void add(EntityComponent *component, const T &data)
{
Expand Down
14 changes: 14 additions & 0 deletions sources/include/cage-core/memoryAllocators.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ namespace cage

CAGE_CORE_API Holder<MemoryArena> newMemoryAllocatorStream(const MemoryAllocatorStreamCreateConfig &config);

// pool allocator with individual deallocations
// both individual deallocations (in any order) and flushing are available
// uses free-list to fill the space in each block
// allocations must fit into the itemSize and itemAlignment

struct CAGE_CORE_API MemoryAllocatorPoolCreateConfig
{
uintPtr itemSize = 0;
uintPtr itemAlignment = 0;
uintPtr blockSize = 4 * 1024 * 1024;
};

CAGE_CORE_API Holder<MemoryArena> newMemoryAllocatorPool(const MemoryAllocatorPoolCreateConfig &config);

// allocator facade for use in std containers

template<class T>
Expand Down
49 changes: 23 additions & 26 deletions sources/libcore/entities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <cage-core/entities.h>
#include <cage-core/flatSet.h>
#include <cage-core/memoryAllocators.h>
#include <cage-core/pointerRangeHolder.h>

namespace cage
Expand All @@ -29,12 +30,15 @@ namespace cage
class EntityManagerImpl : public EntityManager
{
public:
Holder<MemoryArena> arena;
std::vector<Holder<ComponentImpl>> components;
std::vector<EntityComponent *> componentsByTypes;
ankerl::unordered_dense::map<uint32, Entity *> namedEntities;
FlatSet<Entity *> allEntities;
uint32 generateName = 0;

EntityManagerImpl() { arena = newMemoryAllocatorPool({ sizeof(EntityImpl), alignof(EntityImpl) }); }

~EntityManagerImpl()
{
destroy();
Expand All @@ -52,33 +56,34 @@ namespace cage
return generateName++;
}

EntityImpl *newEnt(uint32 name) { return systemMemory().createObject<EntityImpl>(this, name); }
EntityImpl *newEnt(uint32 name) { return arena->createObject<EntityImpl>(this, name); }

void desEnt(EntityImpl *e) { systemMemory().destroy<EntityImpl>(e); }
void desEnt(EntityImpl *e) { arena->destroy<EntityImpl>(e); }
};

class ComponentImpl : public EntityComponent
{
public:
FlatSet<Entity *> componentEntities;
EntityManagerImpl *const manager = nullptr;
void *prototype = nullptr;
Holder<MemoryArena> arena;
Holder<void> prototype;
const uint32 typeIndex = m;
const uint32 typeSize = m;
const uint32 typeAlignment = m;
const uint32 definitionIndex = m;

ComponentImpl(EntityManagerImpl *manager, uint32 typeIndex, const void *prototype_) : manager(manager), typeIndex(typeIndex), typeSize(detail::typeSizeByIndex(typeIndex)), definitionIndex(numeric_cast<uint32>(manager->components.size()))
ComponentImpl(EntityManagerImpl *manager, uint32 typeIndex, const void *prototype_) : manager(manager), typeIndex(typeIndex), typeSize(detail::typeSizeByIndex(typeIndex)), typeAlignment(detail::typeAlignmentByIndex(typeIndex)), definitionIndex(numeric_cast<uint32>(manager->components.size()))
{
CAGE_ASSERT(typeSize > 0);
prototype = newVal();
detail::memcpy(prototype, prototype_, typeSize);
prototype = systemMemory().createBuffer(typeSize, typeAlignment).cast<void>();
detail::memcpy(+prototype, prototype_, typeSize);
arena = newMemoryAllocatorPool({ std::max(typeSize, uint32(sizeof(uintPtr))), typeAlignment });
}

~ComponentImpl() { desVal(prototype); }

void *newVal() { return systemMemory().allocate(typeSize, detail::typeAlignmentByIndex(typeIndex)); }
void *newVal() { return arena->allocate(typeSize, typeAlignment); }

void desVal(void *v) { systemMemory().deallocate(v); }
void desVal(void *v) { arena->deallocate(v); }
};

EntityImpl::EntityImpl(EntityManagerImpl *manager, uint32 name) : manager(manager), name(name)
Expand Down Expand Up @@ -244,7 +249,7 @@ namespace cage

EntityComponent *EntityManager::defineComponent(EntityComponent *source)
{
return defineComponent_(source->typeIndex(), ((ComponentImpl *)source)->prototype);
return defineComponent_(source->typeIndex(), +((ComponentImpl *)source)->prototype);
}

Holder<EntityManager> newEntityManager()
Expand Down Expand Up @@ -295,19 +300,6 @@ namespace cage
return impl->name;
}

void Entity::add(EntityComponent *component)
{
EntityImpl *impl = (EntityImpl *)this;
ComponentImpl *ci = (ComponentImpl *)component;
if (impl->components[ci->definitionIndex] != nullptr)
return;
CAGE_ASSERT(ci->definitionIndex < impl->components.size());
void *tg = impl->components[ci->definitionIndex] = ci->newVal();
detail::memcpy(tg, ci->prototype, ci->typeSize);
ci->componentEntities.insert(this);
ci->entityAdded.dispatch(this);
}

void Entity::remove(EntityComponent *component)
{
EntityImpl *impl = (EntityImpl *)this;
Expand All @@ -331,11 +323,16 @@ namespace cage

void *Entity::unsafeValue(EntityComponent *component)
{
add(component);
EntityImpl *impl = (EntityImpl *)this;
ComponentImpl *ci = (ComponentImpl *)component;
CAGE_ASSERT(ci->definitionIndex < impl->components.size());
CAGE_ASSERT(impl->components[ci->definitionIndex]);
if (impl->components[ci->definitionIndex] == nullptr)
{
void *tg = impl->components[ci->definitionIndex] = ci->newVal();
detail::memcpy(tg, +ci->prototype, ci->typeSize);
ci->componentEntities.insert(this);
ci->entityAdded.dispatch(this);
}
return impl->components[ci->definitionIndex];
}

Expand Down
73 changes: 71 additions & 2 deletions sources/libcore/memory/allocators.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace cage
CAGE_ASSERT(blocks.size() > 0);
CAGE_ASSERT(index < blocks.size());
where = (uintPtr)blocks[index].data();
end = where + blocks[index].capacity();
end = where + blocks[index].size();
}

void nextBuffer()
Expand Down Expand Up @@ -80,7 +80,7 @@ namespace cage
CAGE_ASSERT(blocks.size() > 0);
CAGE_ASSERT(index < blocks.size());
where = (uintPtr)blocks[index]->data();
end = where + blocks[index]->capacity();
end = where + blocks[index]->size();
}

void nextBuffer()
Expand Down Expand Up @@ -138,6 +138,69 @@ namespace cage
uintPtr end = 0; // pointer at the end of the capacity of the current buffer
uint32 index = 0; // index of the current buffer
};

struct MemoryAllocatorPoolImpl : private Immovable
{
explicit MemoryAllocatorPoolImpl(const MemoryAllocatorPoolCreateConfig &config) : config(config)
{
CAGE_ASSERT(config.itemSize >= sizeof(uintPtr));
CAGE_ASSERT(config.itemAlignment > 0);
CAGE_ASSERT((config.itemSize % config.itemAlignment) == 0);
CAGE_ASSERT(config.itemSize + config.itemAlignment + sizeof(uintPtr) <= config.blockSize);
}

void addBlock(MemoryBuffer &b)
{
char *begin = b.data();
char *end = b.data() + b.size();
char *p = (char *)detail::roundDownTo((uintPtr)end, config.itemAlignment) - config.itemSize;
while (p >= begin)
{
deallocate(p);
p -= config.itemSize; // form the free list in reverse so that consecutive allocations have increasing addresses
}
}

void newBlock()
{
blocks.emplace_back(config.blockSize);
addBlock(blocks.back());
}

void *allocate(uintPtr size, uintPtr alignment)
{
CAGE_ASSERT(size <= config.itemSize);
CAGE_ASSERT((config.itemAlignment % alignment) == 0);
if (!freeList)
newBlock();
CAGE_ASSERT(freeList);
void *res = freeList;
void *prev = *(void **)freeList;
freeList = prev;
#ifdef CAGE_DEBUG
*(uintPtr *)res = 0xdeadbeef;
#endif // CAGE_DEBUG
return res;
}

void deallocate(void *ptr)
{
*(void **)ptr = freeList;
freeList = ptr;
}

void flush()
{
freeList = nullptr;
for (MemoryBuffer &b : blocks)
addBlock(b);
}

MemoryArena arena = MemoryArena(this);
const MemoryAllocatorPoolCreateConfig config;
std::vector<MemoryBuffer> blocks;
void *freeList = nullptr;
};
}

Holder<MemoryArena> newMemoryAllocatorLinear(const MemoryAllocatorLinearCreateConfig &config)
Expand All @@ -151,4 +214,10 @@ namespace cage
Holder<MemoryAllocatorStreamImpl> b = systemMemory().createHolder<MemoryAllocatorStreamImpl>(config);
return Holder<MemoryArena>(&b->arena, std::move(b));
}

Holder<MemoryArena> newMemoryAllocatorPool(const MemoryAllocatorPoolCreateConfig &config)
{
Holder<MemoryAllocatorPoolImpl> b = systemMemory().createHolder<MemoryAllocatorPoolImpl>(config);
return Holder<MemoryArena>(&b->arena, std::move(b));
}
}
114 changes: 114 additions & 0 deletions sources/test-core/memoryAllocators.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,119 @@ namespace
}
}

void testPool()
{
CAGE_TESTCASE("pool allocator");

{
CAGE_TESTCASE("flush empty arena");
Holder<MemoryArena> arena = newMemoryAllocatorPool({ 16, 8 });
arena->flush();
}

{
CAGE_TESTCASE("basics raw");
Holder<MemoryArena> arena = newMemoryAllocatorPool({ 16, 16 });
std::vector<uint8 *> v;
v.reserve(100);
for (uint32 i = 0; i < 100; i++)
{
uint8 *p = (uint8 *)arena->allocate(16, 16);
construct(p, 16);
v.push_back(p);
}
for (const uint8 *p : v)
destruct(p, 16);
arena->flush();
}

{
CAGE_TESTCASE("basics structs");
Holder<MemoryArena> arena = newMemoryAllocatorPool({ 64, 8 });
CAGE_TEST(Test<16>::count == 0); // sanity check
std::vector<Holder<Test<16>>> v;
v.reserve(100);
for (uint32 i = 0; i < 100; i++)
v.push_back(arena->createHolder<Test<16>>());
CAGE_TEST(Test<16>::count == 100);
for (const auto &it : v)
it->check();
v.clear();
CAGE_TEST(Test<16>::count == 0);
}

{
CAGE_TESTCASE("large structs");
Holder<MemoryArena> arena = newMemoryAllocatorPool({ 352, 8 });
CAGE_TEST(Test<320>::count == 0); // sanity check
std::vector<Holder<Test<320>>> v;
v.reserve(100);
for (uint32 i = 0; i < 100; i++)
v.push_back(arena->createHolder<Test<320>>());
CAGE_TEST(Test<320>::count == 100);
for (const auto &it : v)
it->check();
v.clear();
CAGE_TEST(Test<320>::count == 0);
}

{
CAGE_TESTCASE("over-aligned structs");
Holder<MemoryArena> arena = newMemoryAllocatorPool({ 352, 16 });
CAGE_TEST(Test<320>::count == 0); // sanity check
std::vector<Holder<AlignedTest<320, 16>>> v;
v.reserve(100);
for (uint32 i = 0; i < 100; i++)
v.push_back(arena->createHolder<AlignedTest<320, 16>>());
CAGE_TEST(Test<320>::count == 100);
for (const auto &it : v)
it->check();
v.clear();
CAGE_TEST(Test<320>::count == 0);
}

{
CAGE_TESTCASE("random order deallocations");
Holder<MemoryArena> arena = newMemoryAllocatorPool({ 64, 8 });
CAGE_TEST(Test<16>::count == 0); // sanity check
std::vector<Holder<Test<16>>> v;
v.reserve(100);
for (uint32 i = 0; i < 100; i++)
v.push_back(arena->createHolder<Test<16>>());
CAGE_TEST(Test<16>::count == 100);
for (const auto &it : v)
it->check();
for (uint32 i = 0; i < 100; i++)
v.erase(v.begin() + randomRange(uintPtr(0), v.size()));
CAGE_TEST(Test<16>::count == 0);
}

{
CAGE_TESTCASE("randomized allocations");
// put the allocator under a lot of pressure to test multiple blocks
Holder<MemoryArena> arena = newMemoryAllocatorPool({ 1024, 256, 4096 });
std::vector<void *> v;
v.reserve(1000);
for (uint32 round = 0; round < 100; round++)
{
for (uint32 i = 0; i < 10; i++)
v.push_back(arena->allocate(randomRange(1, 1000), uintPtr(1) << randomRange(2, 8)));
for (uint32 i = 0; i < 8; i++)
{
const uint32 k = numeric_cast<uint32>(randomRange(uintPtr(0), v.size()));
arena->deallocate(v[k]);
v.erase(v.begin() + k);
}
}
while (!v.empty())
{
const uint32 k = numeric_cast<uint32>(randomRange(uintPtr(0), v.size()));
arena->deallocate(v[k]);
v.erase(v.begin() + k);
}
}
}

void testStd()
{
CAGE_TESTCASE("std allocator");
Expand Down Expand Up @@ -302,5 +415,6 @@ void testMemoryAllocators()

testLinear();
testStream();
testPool();
testStd();
}

0 comments on commit c69dd90

Please sign in to comment.