Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ChainedArenaAllocator #478

Merged
merged 1 commit into from
Dec 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ class ArenaAllocator
raw_data.resize (new_size_bytes, {});
}

/** Resets the allocator */
/**
* Moves the allocator "stack pointer" back to zero,
* effectively "reclaiming" all allocated memory.
*/
void clear() noexcept
{
bytes_used = 0;
Expand Down Expand Up @@ -70,15 +73,15 @@ class ArenaAllocator
* Once the frame goes out of scope, the allocator will be reset
* to whatever it's state was at the beginning of the frame.
*/
struct ArenaAllocatorFrame
struct Frame
{
explicit ArenaAllocatorFrame (ArenaAllocator& allocator)
explicit Frame (ArenaAllocator& allocator)
: alloc (allocator),
bytes_used_at_start (alloc.bytes_used)
{
}

~ArenaAllocatorFrame()
~Frame()
{
alloc.bytes_used = bytes_used_at_start;
}
Expand All @@ -87,6 +90,15 @@ class ArenaAllocator
const size_t bytes_used_at_start;
};

/** Deprecated alias for arena allocator frame */
using ArenaAllocatorFrame [[deprecated]] = Frame;

/** Creates a frame for this allocator */
auto create_frame()
{
return Frame { *this };
}

private:
std::vector<std::byte> raw_data {};
size_t bytes_used = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#pragma once

#include <forward_list>

namespace chowdsp
{
class ChainedArenaAllocator
{
public:
ChainedArenaAllocator() = default;

/** Constructs the arena with an initial allocated size. */
explicit ChainedArenaAllocator (size_t size_in_bytes)
{
reset (size_in_bytes);
}

ChainedArenaAllocator (const ChainedArenaAllocator&) = delete;
ChainedArenaAllocator& operator= (const ChainedArenaAllocator&) = delete;

ChainedArenaAllocator (ChainedArenaAllocator&&) noexcept = default;
ChainedArenaAllocator& operator= (ChainedArenaAllocator&&) noexcept = default;

/**
* Selects the size used for each of the internal arena allocators,
* and "resets" the allocator back to a single arena with that size.
*/
void reset (size_t head_arena_size_bytes)
{
arena_size_bytes = head_arena_size_bytes;

arenas.clear();

arenas.emplace_front (arena_size_bytes);
current_arena = arenas.begin();
arena_count = 1;
}

/**
* Moves the allocator "stack pointer" back to zero,
* effectively "reclaiming" all allocated memory.
*/
void clear() noexcept
{
current_arena = arenas.begin();
get_current_arena().clear();
}

/** Allocates a given number of bytes */
void* allocate_bytes (size_t num_bytes, size_t alignment = 1) noexcept
{
if (num_bytes > arena_size_bytes)
{
jassertfalse;
return nullptr;
}

auto pointer = get_current_arena().allocate_bytes (num_bytes, alignment);
if (pointer != nullptr)
return pointer;

add_arena_to_chain();
return allocate_bytes (num_bytes, alignment);
}

/** Allocates space for some number of objects of type T */
template <typename T, typename IntType>
T* allocate (IntType num_Ts, size_t alignment = alignof (T)) noexcept
{
return static_cast<T*> (allocate_bytes ((size_t) num_Ts * sizeof (T), alignment));
}

/** Returns a pointer to the head allocator with a given offset in bytes */
template <typename T, typename IntType>
T* data (IntType offset_bytes) noexcept
{
return get_current_arena().data<T> (offset_bytes);
}

/** Returns the arena currently being used */
ArenaAllocator& get_current_arena()
{
jassert (current_arena != arenas.end());
return *current_arena;
}

/** Returns a list of the allocator's internal arenas */
[[nodiscard]] auto& get_arenas() const noexcept
{
return arenas;
}

/** Returns the number of arenas currently allocated */
[[nodiscard]] size_t get_arena_count() const noexcept
{
return arena_count;
}

struct Frame
{
explicit Frame (ChainedArenaAllocator& allocator)
: alloc (allocator),
arena_at_start (alloc.current_arena),
arena_frame (alloc.get_current_arena())
{
}

~Frame()
{
alloc.current_arena = arena_at_start;
}

ChainedArenaAllocator& alloc;
const std::forward_list<ArenaAllocator>::iterator arena_at_start;
ArenaAllocator::Frame arena_frame;
};

/** Creates a frame for this allocator */
auto create_frame()
{
return Frame { *this };
}

private:
void add_arena_to_chain()
{
const auto prev_arena = current_arena;
current_arena++;

// if we've reached the end of the list, then we need to
// add a new arena to the chain (starting after the previous arena)
if (current_arena == arenas.end())
{
current_arena = arenas.emplace_after (prev_arena, arena_size_bytes);
arena_count++;
return;
}

get_current_arena().clear();
}

std::forward_list<ArenaAllocator> arenas {};
std::forward_list<ArenaAllocator>::iterator current_arena {};
size_t arena_size_bytes = 0;
size_t arena_count = 0;
};
} // namespace chowdsp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ BEGIN_JUCE_MODULE_DECLARATION
#include "Structures/chowdsp_OptionalPointer.h"
#include "Structures/chowdsp_RawObject.h"
#include "Structures/chowdsp_SmallVector.h"
#include "Structures/chowdsp_ArenaAllocator.h"
#include "Structures/chowdsp_StringLiteral.h"

#include "Helpers/chowdsp_ArrayHelpers.h"
Expand All @@ -39,3 +38,6 @@ BEGIN_JUCE_MODULE_DECLARATION

#include "Structures/chowdsp_AbstractTree.h"
#include "Structures/chowdsp_BucketArray.h"

#include "Allocators/chowdsp_ArenaAllocator.h"
#include "Allocators/chowdsp_ChainedArenaAllocator.h"
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ TEST_CASE ("Arena Allocator Test", "[common][data-structures]")

// allocate with stack frame
{
chowdsp::ArenaAllocator::ArenaAllocatorFrame frame { allocator };
const auto frame = allocator.create_frame();
auto* some_chars = allocator.allocate<char> (30);
juce::ignoreUnused (some_chars);
REQUIRE (allocator.get_bytes_used() == 150);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ target_sources(chowdsp_data_structures_test
OptionalPointerTest.cpp
SmallVectorTest.cpp
ArenaAllocatorTest.cpp
ChainedArenaAllocatorTest.cpp
StringLiteralTest.cpp
TupleHelpersTest.cpp
VectorHelpersTest.cpp
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include <CatchUtils.h>
#include <chowdsp_data_structures/chowdsp_data_structures.h>

TEST_CASE ("Chained Arena Allocator Test", "[common][data-structures]")
{
chowdsp::ChainedArenaAllocator allocator { 100 };

SECTION ("Basic Usage")
{
// allocate doubles
{
auto* some_doubles = allocator.allocate<double> (10);
REQUIRE ((void*) some_doubles == allocator.data<void> (0));
REQUIRE (allocator.get_arena_count() == 1);
REQUIRE (allocator.get_arenas().front().get_bytes_used() == 80);

auto* some_more_doubles = allocator.allocate<double> (10);
REQUIRE (some_more_doubles != nullptr);
REQUIRE (allocator.get_arena_count() == 2);
REQUIRE (allocator.get_arenas().front().get_bytes_used() == 80);
REQUIRE ((allocator.get_arenas().begin()++)->get_bytes_used() == 80);
}

// overfull allocation
REQUIRE (allocator.allocate<double> (200) == nullptr);

// clear allocator
allocator.clear();
REQUIRE (allocator.get_arena_count() == 2);
REQUIRE (allocator.get_current_arena().get_bytes_used() == 0);

// re-allocate doubles
{
auto* some_doubles = allocator.allocate<double> (10);
REQUIRE ((void*) some_doubles == allocator.data<void> (0));
REQUIRE (allocator.get_arenas().front().get_bytes_used() == 80);

auto* some_more_doubles = allocator.allocate<double> (10);
REQUIRE (some_more_doubles != nullptr);
REQUIRE (allocator.get_arena_count() == 2);
REQUIRE (allocator.get_arenas().front().get_bytes_used() == 80);
REQUIRE ((allocator.get_arenas().begin()++)->get_bytes_used() == 80);
}
}

SECTION ("Usage with Frame")
{
{
const auto frame = allocator.create_frame();

auto* some_doubles = allocator.allocate<double> (10);
REQUIRE ((void*) some_doubles == allocator.data<void> (0));
REQUIRE (allocator.get_arena_count() == 1);
REQUIRE (allocator.get_arenas().front().get_bytes_used() == 80);

auto* some_more_doubles = allocator.allocate<double> (10);
REQUIRE (some_more_doubles != nullptr);
REQUIRE (allocator.get_arena_count() == 2);
REQUIRE (allocator.get_arenas().front().get_bytes_used() == 80);
REQUIRE ((allocator.get_arenas().begin()++)->get_bytes_used() == 80);
}

REQUIRE (allocator.get_arena_count() == 2);
REQUIRE (allocator.get_arenas().front().get_bytes_used() == 0);
}
}
Loading