Skip to content

Commit

Permalink
pw_allocator: Add SplitFreeListAllocator
Browse files Browse the repository at this point in the history
This CL adds SplitFreeListAllocator, a memory allocator which uses a
free list. It splits allocations by allocating large and small requests
from opposite ends of its overall memory region. Splitting allocations
by size reduces fragmentation.

Change-Id: I24e5791695f7d2892eb89b4b4371e85288b6cdb5
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/172231
Reviewed-by: Taylor Cramer <[email protected]>
Commit-Queue: Aaron Green <[email protected]>
  • Loading branch information
nopsledder authored and CQ Bot Account committed Sep 24, 2023
1 parent 6b125b9 commit 2ff4f59
Show file tree
Hide file tree
Showing 7 changed files with 766 additions and 0 deletions.
30 changes: 30 additions & 0 deletions pw_allocator/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,23 @@ pw_cc_library(
],
)

pw_cc_library(
name = "split_free_list_allocator",
srcs = [
"split_free_list_allocator.cc",
],
hdrs = [
"public/pw_allocator/split_free_list_allocator.h",
],
includes = ["public"],
deps = [
":allocator",
"//pw_assert",
"//pw_bytes",
"//pw_status",
],
)

pw_cc_library(
name = "allocator_testing",
srcs = [
Expand Down Expand Up @@ -178,3 +195,16 @@ pw_cc_test(
":freelist_heap",
],
)

pw_cc_test(
name = "split_free_list_allocator_test",
srcs = [
"split_free_list_allocator_test.cc",
],
deps = [
":split_free_list_allocator",
"//pw_bytes",
"//pw_containers:vector",
"//pw_unit_test",
],
)
24 changes: 24 additions & 0 deletions pw_allocator/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,28 @@ pw_source_set("freelist_heap") {
sources = [ "freelist_heap.cc" ]
}

pw_source_set("split_free_list_allocator") {
public_configs = [ ":default_config" ]
public = [ "public/pw_allocator/split_free_list_allocator.h" ]
public_deps = [
":allocator",
dir_pw_status,
]
deps = [
dir_pw_assert,
dir_pw_bytes,
]
sources = [ "split_free_list_allocator.cc" ]
}

pw_test_group("tests") {
tests = [
":allocator_test",
":block_test",
":fallback_allocator_test",
":freelist_test",
":freelist_heap_test",
":split_free_list_allocator_test",
]
}

Expand Down Expand Up @@ -167,6 +182,15 @@ pw_test("freelist_heap_test") {
sources = [ "freelist_heap_test.cc" ]
}

pw_test("split_free_list_allocator_test") {
deps = [
":split_free_list_allocator",
"$dir_pw_containers:vector",
dir_pw_bytes,
]
sources = [ "split_free_list_allocator_test.cc" ]
}

pw_doc_group("docs") {
inputs = [ "doc_resources/pw_allocator_heap_visualizer_demo.png" ]
sources = [ "docs.rst" ]
Expand Down
28 changes: 28 additions & 0 deletions pw_allocator/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ pw_add_library(pw_allocator.freelist_heap STATIC
freelist_heap.cc
)

pw_add_library(pw_allocator.split_free_list_allocator STATIC
SOURCES
split_free_list_allocator.cc
HEADERS
public/pw_allocator/split_free_list_allocator.h
PUBLIC_INCLUDES
public
PUBLIC_DEPS
pw_allocator.allocator
pw_status
PRIVATE_DEPS
pw_assert
pw_bytes
)

pw_add_library(pw_allocator.allocator_testing STATIC
HEADERS
pw_allocator_private/allocator_testing.h
Expand Down Expand Up @@ -150,3 +165,16 @@ pw_add_test(pw_allocator.freelist_heap_test
modules
pw_allocator
)

pw_add_test(pw_allocator.split_free_list_allocator_test
SOURCES
split_free_list_allocator_test.cc
PRIVATE_DEPS
pw_allocator.split_free_list_allocator
pw_containers.vector
pw_bytes
pw_unit_test
GROUPS
modules
pw_allocator
)
3 changes: 3 additions & 0 deletions pw_allocator/docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ Provided implementations of the ``Allocator`` interface include:

- ``FallbackAllocator``: Dispatches first to a primary allocator, and, if that
fails, to a secondary alloator.
- ``SplitFreeListAllocator``: Tracks free blocks using a free list, and splits
large and small allocations between the front and back, respectively, of its
memory region in order to reduce fragmentation.

Heap Poisoning
==============
Expand Down
92 changes: 92 additions & 0 deletions pw_allocator/public/pw_allocator/split_free_list_allocator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once

#include <cstddef>
#include <cstdint>
#include <optional>

#include "pw_allocator/allocator.h"
#include "pw_status/status.h"

namespace pw::allocator {

/// This memory allocator uses a free list to track unallocated blocks, with a
/// twist: Allocations above or below a given threshold are taken from
/// respectively lower or higher addresses from within the allocator's memory
/// region. This has the effect of decreasing fragmentation as similarly-sized
/// allocations are grouped together.
///
/// NOTE!! Do NOT use memory returned from this allocator as the backing for
/// another allocator. If this is done, the `Query` method will incorrectly
/// think pointers returned by that alloator were created by this one, and
/// report that this allocator can de/reallocate them.
class SplitFreeListAllocator : public Allocator {
public:
/// Free memory blocks are tracked using a singly linked list. The free memory
/// itself is used to for these structs, so the minimum size and alignment
/// supported by this allocator is `sizeof(FreeBlock)`.
///
/// Allocator callers should not need to access this type directly.
struct FreeBlock {
FreeBlock* next;
size_t size;
};

constexpr SplitFreeListAllocator() = default;
~SplitFreeListAllocator() override;

// Not copyable.
SplitFreeListAllocator(const SplitFreeListAllocator&) = delete;
SplitFreeListAllocator& operator=(const SplitFreeListAllocator&) = delete;

/// Sets the memory region to be used by this allocator, and the threshold at
/// which allocations are considerd "large" or "small". Large and small
/// allocations return lower and higher addresses, respectively.
///
/// @param[in] base Start of the memory region for this allocator.
/// @param[in] size Length of the memory region for this allocator.
/// @param[in] threshold Allocations of this size of larger are considered
/// "large" and come from lower addresses.
void Initialize(void* base, size_t size, size_t threshold);

private:
/// Adds the given block to the free list. The block must not be null.
void AddBlock(FreeBlock* block);

/// Removes the given block from the free list. The block must not be null.
FreeBlock* RemoveBlock(FreeBlock* prev, FreeBlock* block);

/// @copydoc Allocator::Dispatch
Status DoQuery(const void* ptr, size_t size, size_t alignment) const override;

/// @copydoc Allocator::Allocate
void* DoAllocate(size_t size, size_t alignment) override;

/// @copydoc Allocator::Deallocate
void DoDeallocate(void* ptr, size_t size, size_t alignment) override;

/// @copydoc Allocator::Resize
bool DoResize(void* ptr,
size_t old_size,
size_t old_alignment,
size_t new_size) override;

uintptr_t addr_ = 0;
size_t size_ = 0;
FreeBlock* head_ = nullptr;
size_t threshold_ = 0;
};

} // namespace pw::allocator
Loading

0 comments on commit 2ff4f59

Please sign in to comment.