Skip to content

Commit

Permalink
Add ChainedArenaAllocator (#478)
Browse files Browse the repository at this point in the history
  • Loading branch information
jatinchowdhury18 authored Dec 23, 2023
1 parent 89d60ab commit e14121c
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 6 deletions.
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);
}
}

0 comments on commit e14121c

Please sign in to comment.