Skip to content
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
97 changes: 66 additions & 31 deletions include/realtime_memory/free_list_resource.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
// Copyright (c) 2019-2022 CradleApps, LLC - All Rights Reserved
//==============================================================================
/** A std::pmr compatible memory_resource that can allocate and
* free blocks of any size from a fixed buffer. If the buffer
* is exhausted, std::bad_alloc() is thrown.
* free blocks of any size. It can be supplied from an upstream
* resource, a fixed buffer, or a series of fixed buffers pushed
* in by an external routine (to allow async allocation techniques).
*
* Important notes:
* - This resource is single-threaded
Expand All @@ -30,14 +31,42 @@ namespace cradle::pmr
class free_list_resource : public cradle::pmr::identity_equal_resource
{
public:
free_list_resource (void* buffer, std::size_t buffer_size)
: space (buffer_size)
/** Allocate from a buffer. Allocations throw when this runs out. */
free_list_resource (std::byte* buffer, std::size_t buffer_size)
{
if (buffer == nullptr || buffer_size < sizeof(mem_block))
expand (buffer, buffer_size);
}

/** Allocate from an upstream memory resource.
*
* The min_realloc_size describes the minimum size of the memory
* chunk requested from the upstream when the current one runs out.
*/
free_list_resource (memory_resource& upstream_resource,
std::size_t initial_size,
std::size_t min_chunk_size = std::size_t (64 * 1024))
: upstream (&upstream_resource),
min_realloc_size (min_chunk_size)
{
expand (upstream->allocate (initial_size, 1), initial_size);
}

/** Add more memory that can be used for allocations.
* If you supplied a capable upstream_resource constructor, you don't
* need to use this and more memory will be requested from the upstream
* resource if needed.
*
* However, if you are working in a domain where you can allocate/replenish
* memory at certain times and not others, this allows you to do that.
*/
void expand (void* new_memory, std::size_t new_memory_size)
{
if (new_memory == nullptr || new_memory_size < sizeof(mem_block))
throw std::bad_alloc();

auto pos = align_block (buffer, space);
first_free = ::new (pos) mem_block (nullptr, space - mem_block::header_size());
auto pos = align_block (new_memory, new_memory_size);
space += new_memory_size;
first_free = ::new (pos) mem_block (first_free, new_memory_size - mem_block::header_size());
}

/** Returns the remaining space in the buffer, in bytes. */
Expand All @@ -59,9 +88,10 @@ class free_list_resource : public cradle::pmr::identity_equal_resource
throw std::bad_alloc(); // Over-alignment is not currently supported!

const auto size = cradle::pmr::detail::aligned_ceil (bytes, std::min (align, max_align_bytes));
const auto next_chunk_size = std::max (min_realloc_size, size + mem_block::header_size());

if ((size + mem_block::header_size()) >= space)
throw std::bad_alloc(); // out of memory.
expand (upstream->allocate (next_chunk_size, max_align_bytes), next_chunk_size);

for (mem_block* free = first_free, *prev = nullptr; free != nullptr; prev = free, free = free->next)
if (free->size >= size)
Expand All @@ -71,7 +101,9 @@ class free_list_resource : public cradle::pmr::identity_equal_resource
if (auto defragmented_block = defragment (size))
return defragmented_block;

throw std::bad_alloc(); // still too fragmented, abort
// Still too fragmented: we'll have to allocate another chunk.
expand (upstream->allocate (next_chunk_size, max_align_bytes), next_chunk_size);
return do_allocate (bytes, align);
}

void do_deallocate (void* ptr, std::size_t, std::size_t) override
Expand Down Expand Up @@ -134,37 +166,38 @@ class free_list_resource : public cradle::pmr::identity_equal_resource
mem_block* closest_sized_block = nullptr;
mem_block* closest_sized_block_prev = nullptr;

for (mem_block* free = first_free, *prev = nullptr; free != nullptr && free->next != nullptr;)
for (mem_block* free = first_free, *prev = nullptr; free != nullptr;)
{
auto next = free->next;
const auto thisStart = free->data();
const auto thisEnd = thisStart + free->size;
const auto nextHeader = reinterpret_cast<std::byte*> (next);

if (nextHeader - thisEnd <= std::ptrdiff_t (max_align_bytes))
// merging next block along if it's free
while (free->next)
{
const auto nextEnd = next->data() + next->size;
auto next = free->next;
auto thisEnd = free->data() + free->size;
auto nextHeader = reinterpret_cast<std::byte*> (next);

if (nextHeader - thisEnd > std::ptrdiff_t (max_align_bytes))
break;

free->size = std::size_t (nextEnd - thisStart);
auto nextEnd = next->data() + next->size;
free->size = std::size_t (nextEnd - free->data());
free->next = next->next;
}
else

// look for a block that is suitable for use by the caller
if (free->size >= desired_block_size)
{
if (free->size >= desired_block_size)
const auto diff = free->size - desired_block_size;

if (diff < closest_size_diff)
{
const auto diff = free->size - desired_block_size;

if (diff < closest_size_diff)
{
closest_size_diff = diff;
closest_sized_block = free;
closest_sized_block_prev = prev;
}
closest_size_diff = diff;
closest_sized_block = free;
closest_sized_block_prev = prev;
}

prev = free;
free = free->next;
}

prev = free;
free = free->next;
}

if (closest_sized_block == nullptr)
Expand Down Expand Up @@ -235,9 +268,11 @@ class free_list_resource : public cradle::pmr::identity_equal_resource
static constexpr auto max_align_bytes = alignof (std::max_align_t);

//==============================================================================
memory_resource* upstream = null_memory_resource();
mem_block* first_free = nullptr;
std::size_t space = 0;
std::size_t num_allocs = 0;
std::size_t min_realloc_size = 0;
};

} // namespace cradle
6 changes: 3 additions & 3 deletions include/realtime_memory/memory_resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ inline monotonic_buffer_resource::monotonic_buffer_resource (std::size_t initial
{}

inline monotonic_buffer_resource::monotonic_buffer_resource (std::size_t initial_size,
memory_resource* up) noexcept
memory_resource* up) noexcept
: upstream (up ? *up : *get_default_resource()),
currentbuf (nullptr),
currentbuf_size (0),
Expand All @@ -405,8 +405,8 @@ inline monotonic_buffer_resource::monotonic_buffer_resource (void* buf, std::siz


inline monotonic_buffer_resource::monotonic_buffer_resource (void* buf,
std::size_t bufsize,
memory_resource* up) noexcept
std::size_t bufsize,
memory_resource* up) noexcept
: upstream (up ? *up : *get_default_resource()),
currentbuf (buf),
currentbuf_size (bufsize),
Expand Down
2 changes: 2 additions & 0 deletions include/realtime_memory/pmr_includes.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include <algorithm>
#include <atomic>
#include <cassert>
#include <stdexcept>
#include <utility>
#include <vector>

#if defined (__apple_build_version__)
Expand Down
2 changes: 1 addition & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.12)
Include(FetchContent)
FetchContent_Declare(Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.0.1) # or a later release
GIT_TAG v3.11.0) # or a later release
FetchContent_MakeAvailable(Catch2)

add_executable(realtime_memory_tests
Expand Down
Loading