Skip to content

Commit

Permalink
Simple Pool Allocator implementation (#549)
Browse files Browse the repository at this point in the history
  • Loading branch information
jatinchowdhury18 authored Aug 25, 2024
1 parent e395426 commit ceb5df6
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#pragma once

namespace chowdsp
{
/**
* A simple pool allocator.
*
* This implementation is based on GingerBill's blog post:
* https://www.gingerbill.org/article/2019/02/16/memory-allocation-strategies-004/
*/
template <size_t chunk_size, size_t alignment>
struct PoolAllocator
{
private:
struct FreeListNode
{
FreeListNode* next = nullptr;
};
FreeListNode* free_list_head = nullptr;

public:
static constexpr size_t chunk_size_padded = ((chunk_size + alignment - 1) / alignment) * alignment;
nonstd::span<std::byte> backing_buffer {};

PoolAllocator() = default;

/** Creates the allocator with some number of chunks pre-allocated. */
explicit PoolAllocator (size_t num_chunks)
{
resize (num_chunks);
}

~PoolAllocator()
{
aligned_free (backing_buffer.data());
}

/** Re-allocates the allocator's backing buffer to fit some number of chunks. */
void resize (size_t num_chunks)
{
aligned_free (backing_buffer.data());

const auto allocator_bytes = chunk_size_padded * num_chunks;
backing_buffer = { static_cast<std::byte*> (aligned_alloc (alignment, allocator_bytes)), allocator_bytes };
free_all();
}

/** Resets the allocator free list. */
void free_all()
{
size_t chunk_count = backing_buffer.size() / chunk_size_padded;
// Set all chunks to be free
for (size_t i = 0; i < chunk_count; ++i)
{
auto* ptr = backing_buffer.data() + i * chunk_size_padded;
auto* node = reinterpret_cast<FreeListNode*> (ptr);

// Push free node onto the free list
node->next = free_list_head;
free_list_head = node;
}
}

/** Allocates a single chunk. */
void* allocate_chunk()
{
// Get latest free node
auto* node = free_list_head;

if (node == nullptr)
{
jassertfalse;
return nullptr;
}

// Pop free node
free_list_head = free_list_head->next;

// Zero memory by default
return memset (node, 0, chunk_size_padded);
}

/** Allocates a chunk and constructs an object in place. */
template <typename T, typename... Args>
T* allocate (Args&&... args)
{
static_assert (sizeof (T) <= chunk_size_padded);
static_assert (alignof (T) <= alignment);

auto* bytes = allocate_chunk();
return new (bytes) T { std::forward<Args> (args)... };
}

/** Frees a single chunk. */
void free_chunk (void* ptr)
{
if (ptr == nullptr)
return;

void* start = backing_buffer.data();
void* end = backing_buffer.data() + static_cast<int> (backing_buffer.size());
if (ptr < start || ptr >= end)
{
// this pointer was not allocated from this data!
jassertfalse;
return;
}

// Push free node
auto* node = static_cast<FreeListNode*> (ptr);
node->next = free_list_head;
free_list_head = node;
}

/** Calls an object's destructor and then frees the memory. */
template <typename T>
void free (T* ptr)
{
if (ptr == nullptr)
return;

void* start = backing_buffer.data();
void* end = backing_buffer.data() + static_cast<int> (backing_buffer.size());
if (ptr < start || ptr >= end)
{
// this pointer was not allocated from this data!
jassertfalse;
return;
}

ptr->~T();
free_chunk (ptr);
}
};
} // namespace chowdsp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ BEGIN_JUCE_MODULE_DECLARATION
#include "Allocators/chowdsp_STLArenaAllocator.h"
#include "Helpers/chowdsp_ArenaHelpers.h"

#include "Allocators/chowdsp_PoolAllocator.h"

#include "Structures/chowdsp_BucketArray.h"
#include "Structures/chowdsp_AbstractTree.h"
#include "Structures/chowdsp_SmallMap.h"
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ target_sources(chowdsp_data_structures_test
EnumMapTest.cpp
OptionalRefTest.cpp
OptionalArrayTest.cpp
PoolAllocatorTest.cpp
)

target_compile_features(chowdsp_data_structures_test PRIVATE cxx_std_20)
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <CatchUtils.h>
#include <chowdsp_data_structures/chowdsp_data_structures.h>

TEST_CASE ("Pool Allocator Test", "[common][data-structures]")
{
SECTION ("Default")
{
chowdsp::PoolAllocator<32, 8> allocator {};
REQUIRE (allocator.backing_buffer.empty());
}

SECTION ("With Size")
{
chowdsp::PoolAllocator<32, 8> allocator { 4 };
REQUIRE (allocator.backing_buffer.size() == 128);
}

SECTION ("Allocate/Free")
{
chowdsp::PoolAllocator<32, 8> allocator { 4 };
std::array<void*, 6> pointers {};
for (size_t i = 0; i < 5; ++i)
{
pointers[i] = allocator.allocate_chunk();
if (i < 4)
REQUIRE (pointers[i] != nullptr);
else
REQUIRE (pointers[i] == nullptr);
}
pointers.back() = &allocator;
for (auto* pointer : pointers)
allocator.free_chunk (pointer);
}

SECTION ("Allocate/Free Object")
{
chowdsp::PoolAllocator<32, 8> allocator { 4 };

struct Thing
{
int64_t x {};
int64_t y {};
int64_t z {};
};
auto* thing = allocator.allocate<Thing> (0, 1, 2);
REQUIRE (thing != nullptr);
REQUIRE (thing->x == 0);
REQUIRE (thing->y == 1);
REQUIRE (thing->z == 2);
allocator.free (thing);

allocator.free<Thing> (nullptr);

Thing xx {};
allocator.free<Thing> (&xx);
}
}

0 comments on commit ceb5df6

Please sign in to comment.