Skip to content

Commit

Permalink
Flat Memory Pool (#572)
Browse files Browse the repository at this point in the history
* Flat pool working on POSIX

* Flat pool working on POSIX

* Flat Pool working on Windows

* Apply clang-format

* Custom get_page_size()

* Fixing MacOS

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
jatinchowdhury18 and github-actions[bot] authored Dec 4, 2024
1 parent 085e2bf commit d170900
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#pragma once

#if ! JUCE_TEENSY

#if JUCE_WINDOWS
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#endif

namespace chowdsp
{
/**
* A "flat" memory pool that reserves a ton of virtual memory,
* and then commits pages of that memory as needed.
*/
struct FlatMemoryPool
{
std::byte* current_pointer {};
std::byte* memory_base {};
std::byte* first_uncommitted_page {};
std::byte* address_limit {};
const size_t page_size { get_page_size() };

void* allocate_bytes (size_t num_bytes, size_t alignment = 8)
{
auto* p = juce::snapPointerToAlignment (current_pointer, alignment);
const auto end = p + num_bytes;

if (end > first_uncommitted_page)
{
if (end > address_limit)
{
// The memory pool is full!
jassertfalse;
return nullptr;
}
else
{
extend_committed_pages (end);
}
}

current_pointer = end;
return p;
}

[[nodiscard]] size_t get_bytes_used() const noexcept
{
return static_cast<size_t> (current_pointer - memory_base);
}

void clear()
{
if (memory_base == nullptr)
return;

#if ARENA_ALLOCATOR_DEBUG
memset (memory_base, 0xcc, static_cast<size_t> (current_pointer - memory_base));
#endif

current_pointer = memory_base;
}

struct Frame
{
FlatMemoryPool& pool;
std::byte* pointer_at_start;

explicit Frame (FlatMemoryPool& frame_pool)
: pool { frame_pool },
pointer_at_start { pool.current_pointer }
{
}

~Frame()
{
pool.current_pointer = pointer_at_start;
}
};

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

void init (size_t reserve_bytes = 1 << 28)
{
const auto reserve_padded = page_size * ((reserve_bytes + page_size - 1) / page_size);

#if JUCE_WINDOWS
memory_base = static_cast<std::byte*> (VirtualAlloc (nullptr, reserve_padded, MEM_RESERVE, PAGE_READWRITE));
#else
memory_base = static_cast<std::byte*> (mmap (nullptr, reserve_padded, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0));
jassert (memory_base != nullptr);
#endif

current_pointer = memory_base;
first_uncommitted_page = memory_base;
address_limit = memory_base + reserve_padded;
}

void deinit()
{
if (memory_base == nullptr)
return;

#if JUCE_WINDOWS
VirtualFree (memory_base, 0, MEM_RELEASE);
#else
munmap (memory_base, static_cast<size_t> (address_limit - memory_base));
#endif

memory_base = nullptr;
}

private:
void extend_committed_pages (std::byte* end)
{
jassert (end - first_uncommitted_page >= 0);
#if JUCE_WINDOWS
const auto size = juce::snapPointerToAlignment (end, page_size) - first_uncommitted_page;
VirtualAlloc (first_uncommitted_page, static_cast<size_t> (size), MEM_COMMIT, PAGE_READWRITE);
first_uncommitted_page += size;
#else
first_uncommitted_page = juce::snapPointerToAlignment (end, page_size);
#endif
}

#if JUCE_WINDOWS
static size_t get_page_size()
{
SYSTEM_INFO systemInfo;
GetNativeSystemInfo (&systemInfo);
return static_cast<size_t> (systemInfo.dwPageSize);
}
#else
static size_t get_page_size()
{
return static_cast<size_t> (sysconf (_SC_PAGESIZE));
}
#endif
};
} // namespace chowdsp

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@

namespace chowdsp::arena
{
/**
* Allocates space for some number of objects of type T
* The returned memory will be un-initialized, so be sure to clear it manually if needed.
*/
template <typename T, typename IntType, typename Arena>
T* allocate (Arena& arena, IntType num_Ts, size_t alignment = alignof (T)) noexcept
{
return static_cast<T*> (arena.allocate_bytes ((size_t) num_Ts * sizeof (T), alignment));
}

template <typename T, typename I, typename Arena>
nonstd::span<T> make_span (Arena& arena, I size, size_t alignment = alignof (T))
{
return { arena.template allocate<T> (size, alignment), static_cast<size_t> (size) };
return { allocate<T> (arena, size, alignment), static_cast<size_t> (size) };
}

template <typename Arena>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ BEGIN_JUCE_MODULE_DECLARATION
#include "Allocators/chowdsp_STLArenaAllocator.h"
#include "Helpers/chowdsp_ArenaHelpers.h"

#include "Allocators/chowdsp_FlatMemoryPool.h"
#include "Allocators/chowdsp_PoolAllocator.h"

#include "Structures/chowdsp_BucketArray.h"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ target_sources(chowdsp_data_structures_test
PackedPointerTest.cpp
ChunkListTest.cpp
FixedSizeFunctionTest.cpp
FlatMemoryPoolTest.cpp
)

target_compile_features(chowdsp_data_structures_test PRIVATE cxx_std_20)
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include <CatchUtils.h>
#include <chowdsp_data_structures/chowdsp_data_structures.h>

TEST_CASE ("Flat Memory Pool Test", "[common][data-structures]")
{
chowdsp::FlatMemoryPool allocator {};
allocator.init();

SECTION ("Basic Usage")
{
// allocate doubles
double* some_doubles;
{
some_doubles = chowdsp::arena::allocate<double> (allocator, 10);
REQUIRE (some_doubles != nullptr);
REQUIRE (allocator.get_bytes_used() == 80);

auto* some_more_doubles = chowdsp::arena::allocate<double> (allocator, 10);
REQUIRE (some_more_doubles != nullptr);
REQUIRE (some_more_doubles == some_doubles + 10);
REQUIRE (allocator.get_bytes_used() == 160);
}

// clear allocator
allocator.clear();

// re-allocate doubles
{
auto* some_doubles2 = chowdsp::arena::allocate<double> (allocator, 10);
REQUIRE (some_doubles2 != nullptr);
REQUIRE (some_doubles2 == some_doubles);
REQUIRE (allocator.get_bytes_used() == 80);

auto* some_more_doubles = chowdsp::arena::allocate<double> (allocator, 10);
REQUIRE (some_more_doubles != nullptr);
REQUIRE (some_more_doubles == some_doubles + 10);
REQUIRE (allocator.get_bytes_used() == 160);
}
}

SECTION ("Overfull Allocation")
{
REQUIRE (chowdsp::arena::allocate<double> (allocator, 20'000) != nullptr);
REQUIRE (chowdsp::arena::allocate<double> (allocator, 200) != nullptr);
REQUIRE (chowdsp::arena::allocate<double> (allocator, 2'000'000) != nullptr);
REQUIRE (allocator.get_bytes_used() == 8 * 2'020'200);
allocator.clear();
REQUIRE (allocator.get_bytes_used() == 0);
}

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

auto* some_doubles = chowdsp::arena::allocate<double> (allocator, 10);
REQUIRE (some_doubles != nullptr);
REQUIRE (allocator.get_bytes_used() == 80);

auto* some_more_doubles = chowdsp::arena::allocate<double> (allocator, 10);
REQUIRE (some_more_doubles != nullptr);
REQUIRE (allocator.get_bytes_used() == 160);
}

REQUIRE (allocator.get_bytes_used() == 0);

allocator.deinit();
REQUIRE (allocator.memory_base == nullptr);
}
}

0 comments on commit d170900

Please sign in to comment.