From d4cfc01ee40508e76d706815d87be66cac577746 Mon Sep 17 00:00:00 2001 From: jatin Date: Sun, 25 Aug 2024 06:40:55 -0700 Subject: [PATCH] Simple Pool Allocator implementation --- .../Allocators/chowdsp_PoolAllocator.h | 135 ++++++++++++++++++ .../chowdsp_data_structures.h | 2 + .../CMakeLists.txt | 1 + .../PoolAllocatorTest.cpp | 57 ++++++++ 4 files changed, 195 insertions(+) create mode 100644 modules/common/chowdsp_data_structures/Allocators/chowdsp_PoolAllocator.h create mode 100644 tests/common_tests/chowdsp_data_structures_test/PoolAllocatorTest.cpp diff --git a/modules/common/chowdsp_data_structures/Allocators/chowdsp_PoolAllocator.h b/modules/common/chowdsp_data_structures/Allocators/chowdsp_PoolAllocator.h new file mode 100644 index 00000000..683b53e0 --- /dev/null +++ b/modules/common/chowdsp_data_structures/Allocators/chowdsp_PoolAllocator.h @@ -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 +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 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 (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 (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 + 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)... }; + } + + /** 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 (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 (ptr); + node->next = free_list_head; + free_list_head = node; + } + + /** Calls an object's destructor and then frees the memory. */ + template + void free (T* ptr) + { + if (ptr == nullptr) + return; + + void* start = backing_buffer.data(); + void* end = backing_buffer.data() + static_cast (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 diff --git a/modules/common/chowdsp_data_structures/chowdsp_data_structures.h b/modules/common/chowdsp_data_structures/chowdsp_data_structures.h index 0ee7a242..02e9ccca 100644 --- a/modules/common/chowdsp_data_structures/chowdsp_data_structures.h +++ b/modules/common/chowdsp_data_structures/chowdsp_data_structures.h @@ -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" diff --git a/tests/common_tests/chowdsp_data_structures_test/CMakeLists.txt b/tests/common_tests/chowdsp_data_structures_test/CMakeLists.txt index 1b0c8d72..b007a8f0 100644 --- a/tests/common_tests/chowdsp_data_structures_test/CMakeLists.txt +++ b/tests/common_tests/chowdsp_data_structures_test/CMakeLists.txt @@ -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) diff --git a/tests/common_tests/chowdsp_data_structures_test/PoolAllocatorTest.cpp b/tests/common_tests/chowdsp_data_structures_test/PoolAllocatorTest.cpp new file mode 100644 index 00000000..3277d3d9 --- /dev/null +++ b/tests/common_tests/chowdsp_data_structures_test/PoolAllocatorTest.cpp @@ -0,0 +1,57 @@ +#include +#include + +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 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 (0, 1, 2); + REQUIRE (thing != nullptr); + REQUIRE (thing->x == 0); + REQUIRE (thing->y == 1); + REQUIRE (thing->z == 2); + allocator.free (thing); + + allocator.free (nullptr); + + Thing xx {}; + allocator.free (&xx); + } +}