Skip to content

Commit

Permalink
Arena Allocator updates (#518)
Browse files Browse the repository at this point in the history
* Arena Allocator updates

* Cleanup

* Apply clang-format

* Teensy fix

* Fixing allocation issues

* ifdef __clang__

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
jatinchowdhury18 and github-actions[bot] authored Apr 1, 2024
1 parent 8843e52 commit 46fe42c
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 45 deletions.
60 changes: 60 additions & 0 deletions modules/common/chowdsp_core/Memory/chowdsp_AlignedAlloc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#pragma once

#include <cstdlib>

namespace chowdsp
{
#if JUCE_TEENSY

/** MSVC-compatible implementation of aligned_alloc */
[[nodiscard]] inline void* aligned_alloc ([[maybe_unused]] size_t alignment, size_t size)
{
// @TODO
return malloc (size + alignment);
}

/** MSVC-compatible implementation of aligned_free */
inline void aligned_free (void* data)
{
free (data);
}

#elif defined(_MSC_VER)

/** MSVC-compatible implementation of aligned_alloc */
[[nodiscard]] inline void* aligned_alloc (size_t alignment, size_t size)
{
return _aligned_malloc (size, alignment);
}

/** MSVC-compatible implementation of aligned_free */
inline void aligned_free (void* data)
{
_aligned_free (data);
}

#else

/** MSVC-compatible implementation of aligned_alloc */
[[nodiscard]] inline void* aligned_alloc (size_t alignment, size_t size)
{
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
#endif

return std::aligned_alloc (alignment, size);

#ifdef __clang__
#pragma clang diagnostic pop
#endif
}

/** MSVC-compatible implementation of aligned_free */
inline void aligned_free (void* data)
{
std::free (data);
}

#endif
} // namespace chowdsp
1 change: 1 addition & 0 deletions modules/common/chowdsp_core/chowdsp_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ namespace experimental

#include "Functional/chowdsp_Bindings.h"
#include "Functional/chowdsp_EndOfScopeAction.h"
#include "Memory/chowdsp_AlignedAlloc.h"
#include "Memory/chowdsp_MemoryAliasing.h"
#include "Types/chowdsp_TypeHasCheckers.h"
#include "Types/chowdsp_TypeTraits.h"
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ class ArenaAllocator
reset (size_in_bytes);
}

template <typename OtherMemoryResourceType,
typename ThisMemoryResourceType = MemoryResourceType,
std::enable_if_t<std::is_same_v<ThisMemoryResourceType, nonstd::span<std::byte>>>* = nullptr>
ArenaAllocator (ArenaAllocator<OtherMemoryResourceType>& other) // NOLINT
: raw_data { other.raw_data },
bytes_used { other.bytes_used }
{
}

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

Expand All @@ -41,9 +50,18 @@ class ArenaAllocator
bytes_used = 0;
}

/** Returns the number of bytes currently being used */
/** Returns the number of bytes currently being used. */
[[nodiscard]] size_t get_bytes_used() const noexcept { return bytes_used; }

/** Returns the total number of bytes the allocator manages. */
[[nodiscard]] size_t get_total_num_bytes() const noexcept { return raw_data.size(); }

/** Returns a reference to the allocator's internal memory resource. */
[[nodiscard]] auto& get_memory_resource() { return raw_data; }

/** Returns a reference to the allocator's internal memory resource. */
[[nodiscard]] const auto& get_memory_resource() const { return raw_data; }

/**
* Allocates a given number of bytes.
* The returned memory will be un-initialized, so be sure to clear it manually if needed.
Expand Down Expand Up @@ -99,9 +117,6 @@ 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()
{
Expand All @@ -110,8 +125,15 @@ class ArenaAllocator

private:
MemoryResourceType raw_data {};
size_t bytes_used = 0;
size_t bytes_used_internal = 0;
size_t& bytes_used = bytes_used_internal;

template <typename>
friend class ArenaAllocator;

CHOWDSP_CHECK_HAS_METHOD (MemoryResourceTypeHasResize, resize, std::declval<size_t>())
};

/** Convenient alias for a "non-owning" arena. */
using ArenaAllocatorView = ArenaAllocator<nonstd::span<std::byte>>;
} // namespace chowdsp
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,10 @@

namespace chowdsp
{
template <typename MemoryResourceType = std::vector<std::byte>>
class ChainedArenaAllocator
{
public:
using BaseArena = ArenaAllocator<MemoryResourceType>;

ChainedArenaAllocator()
{
if (MemoryResourceType test_mrt {}; test_mrt.size() > 0)
reset (test_mrt.size());
}
ChainedArenaAllocator() = default;

/** Constructs the arena with an initial allocated size. */
explicit ChainedArenaAllocator (size_t size_in_bytes)
Expand All @@ -28,6 +21,11 @@ class ChainedArenaAllocator
ChainedArenaAllocator (ChainedArenaAllocator&&) noexcept = default;
ChainedArenaAllocator& operator= (ChainedArenaAllocator&&) noexcept = default;

~ChainedArenaAllocator()
{
free_arenas();
}

/**
* Selects the size used for each of the internal arena allocators,
* and "resets" the allocator back to a single arena with that size.
Expand All @@ -36,10 +34,12 @@ class ChainedArenaAllocator
{
arena_size_bytes = head_arena_size_bytes;

free_arenas();
arenas.clear();

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

Expand Down Expand Up @@ -85,7 +85,7 @@ class ChainedArenaAllocator
}

/** Returns the arena currently being used */
BaseArena& get_current_arena()
ArenaAllocatorView& get_current_arena()
{
jassert (current_arena != arenas.end());
return *current_arena;
Expand All @@ -110,7 +110,7 @@ class ChainedArenaAllocator
* Note that due to the design of the allocator, not all
* of the available bytes may be used at any given time.
* As such, the allocator's "load factor" can be computed
* as: get_total_bytes_used() / (get_arena_count() * arena_size_bytes)
* as: get_total_bytes_used() / get_total_bytes()
*/
[[nodiscard]] size_t get_total_bytes_used() const noexcept
{
Expand All @@ -121,6 +121,18 @@ class ChainedArenaAllocator
return bytes_count;
}

/**
* Returns the total number of bytes being managed by
* this allocator.
*/
[[nodiscard]] size_t get_total_bytes() const noexcept
{
size_t bytes_count = 0;
for (const auto& arena : arenas)
bytes_count += arena.get_total_num_bytes();
return bytes_count;
}

struct Frame
{
explicit Frame (ChainedArenaAllocator& allocator)
Expand All @@ -136,8 +148,8 @@ class ChainedArenaAllocator
}

ChainedArenaAllocator& alloc;
const typename std::forward_list<BaseArena>::iterator arena_at_start;
typename BaseArena::Frame arena_frame;
const std::forward_list<ArenaAllocatorView>::iterator arena_at_start;
ArenaAllocatorView::Frame arena_frame;
};

/** Creates a frame for this allocator */
Expand All @@ -149,23 +161,39 @@ class ChainedArenaAllocator
private:
void add_arena_to_chain()
{
const auto prev_arena = current_arena;
current_arena++;
const auto prev_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);
current_arena = arenas.emplace_after (prev_arena);
allocate_current_arena (arena_size_bytes);
arena_count++;
return;
}

get_current_arena().clear();
}

std::forward_list<BaseArena> arenas {};
typename std::forward_list<BaseArena>::iterator current_arena {};
void allocate_current_arena (size_t num_bytes)
{
static constexpr size_t arena_alignment = 64;
const auto num_bytes_padded = arena_alignment * ((num_bytes + arena_alignment - 1) / arena_alignment);
current_arena->get_memory_resource() = {
static_cast<std::byte*> (aligned_alloc (arena_alignment, num_bytes_padded)),
num_bytes_padded,
};
}

void free_arenas()
{
for (auto& arena : arenas)
aligned_free (arena.get_memory_resource().data());
}

std::forward_list<ArenaAllocatorView> arenas {};
std::forward_list<ArenaAllocatorView>::iterator current_arena {};
size_t arena_size_bytes = 0;
size_t arena_count = 0;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,9 @@ class SmoothedBufferValue
*/
void process (int numSamples);

template <typename Arena>
void process (int numSamples, ArenaAllocator<Arena>& alloc)
void process (int numSamples, ArenaAllocatorView alloc)
{
bufferData = alloc.template allocate<FloatType> (numSamples, bufferAlignment);
bufferData = alloc.allocate<FloatType> (numSamples, bufferAlignment);
jassert (bufferData != nullptr); // arena allocator is out of memory!
process (numSamples);
}
Expand All @@ -93,10 +92,9 @@ class SmoothedBufferValue
*/
void process (FloatType value, int numSamples);

template <typename Arena>
void process (FloatType value, int numSamples, ArenaAllocator<Arena>& alloc)
void process (FloatType value, int numSamples, ArenaAllocatorView alloc)
{
bufferData = alloc.template allocate<FloatType> (numSamples, bufferAlignment);
bufferData = alloc.allocate<FloatType> (numSamples, bufferAlignment);
jassert (bufferData != nullptr); // arena allocator is out of memory!
process (value, numSamples);
}
Expand Down
2 changes: 1 addition & 1 deletion modules/gui/chowdsp_gui/Helpers/chowdsp_ComponentArena.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class ComponentArena
allocator.clear();
}

ChainedArenaAllocator<std::array<std::byte, arena_chunk_size_bytes>> allocator {};
ChainedArenaAllocator allocator { arena_chunk_size_bytes };

private:
SmallVector<DestructiblePointer<juce::Component>, approx_component_count> component_list {};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
#include <CatchUtils.h>
#include <chowdsp_data_structures/chowdsp_data_structures.h>

TEMPLATE_TEST_CASE ("Chained Arena Allocator Test", "[common][data-structures]", (std::vector<std::byte>), (std::array<std::byte, 100>) )
TEST_CASE ("Chained Arena Allocator Test", "[common][data-structures]")
{
using MemoryResourceType = TestType;

auto allocator = []
{
if constexpr (std::is_same_v<MemoryResourceType, std::vector<std::byte>>)
return chowdsp::ChainedArenaAllocator<MemoryResourceType> { 100 };
else
return chowdsp::ChainedArenaAllocator<MemoryResourceType> {};
}();
chowdsp::ChainedArenaAllocator allocator { 100 };

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

auto* some_more_doubles = allocator.template allocate<double> (10);
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);
Expand All @@ -41,12 +33,12 @@ TEMPLATE_TEST_CASE ("Chained Arena Allocator Test", "[common][data-structures]",

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

auto* some_more_doubles = allocator.template allocate<double> (10);
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);
Expand All @@ -60,12 +52,12 @@ TEMPLATE_TEST_CASE ("Chained Arena Allocator Test", "[common][data-structures]",
{
const auto frame = allocator.create_frame();

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

auto* some_more_doubles = allocator.template allocate<double> (10);
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);
Expand Down

0 comments on commit 46fe42c

Please sign in to comment.