-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Simple Pool Allocator implementation (#549)
- Loading branch information
1 parent
e395426
commit ceb5df6
Showing
4 changed files
with
195 additions
and
0 deletions.
There are no files selected for viewing
135 changes: 135 additions & 0 deletions
135
modules/common/chowdsp_data_structures/Allocators/chowdsp_PoolAllocator.h
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
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 |
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
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
57 changes: 57 additions & 0 deletions
57
tests/common_tests/chowdsp_data_structures_test/PoolAllocatorTest.cpp
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
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); | ||
} | ||
} |