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

Arena Allocator updates #518

Merged
merged 7 commits into from
Apr 1, 2024
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
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 @@
*/
void process (int numSamples);

template <typename Arena>
void process (int numSamples, ArenaAllocator<Arena>& alloc)
void process (int numSamples, ArenaAllocatorView alloc)

Check warning on line 82 in modules/dsp/chowdsp_dsp_data_structures/Other/chowdsp_SmoothedBufferValue.h

View check run for this annotation

Codecov / codecov/patch

modules/dsp/chowdsp_dsp_data_structures/Other/chowdsp_SmoothedBufferValue.h#L82

Added line #L82 was not covered by tests
{
bufferData = alloc.template allocate<FloatType> (numSamples, bufferAlignment);
bufferData = alloc.allocate<FloatType> (numSamples, bufferAlignment);

Check warning on line 84 in modules/dsp/chowdsp_dsp_data_structures/Other/chowdsp_SmoothedBufferValue.h

View check run for this annotation

Codecov / codecov/patch

modules/dsp/chowdsp_dsp_data_structures/Other/chowdsp_SmoothedBufferValue.h#L84

Added line #L84 was not covered by tests
jassert (bufferData != nullptr); // arena allocator is out of memory!
process (numSamples);
}
Expand All @@ -93,10 +92,9 @@
*/
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
Loading