From 34cd98e83e47fc570f42ed6e064de59f5e888ac1 Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Thu, 14 Aug 2025 11:43:54 -0400 Subject: [PATCH] Intrusive linked list for arena allocator --- src/arena_allocator.hpp | 123 ++++++++++++++++++++++++++++++++-------- 1 file changed, 100 insertions(+), 23 deletions(-) diff --git a/src/arena_allocator.hpp b/src/arena_allocator.hpp index d14933a..83451e4 100644 --- a/src/arena_allocator.hpp +++ b/src/arena_allocator.hpp @@ -1,39 +1,95 @@ #pragma once #include +#include #include -#include +#include class ArenaAllocator { +private: + struct Block { + size_t size; + Block *next; + + char *data() { return reinterpret_cast(this + 1); } + + static Block *create(size_t size) { + void *memory = std::aligned_alloc(alignof(Block), sizeof(Block) + size); + if (!memory) { + throw std::bad_alloc(); + } + Block *block = new (memory) Block{size, nullptr}; + return block; + } + }; + public: explicit ArenaAllocator(size_t initial_size = 1024) - : initial_block_size_(initial_size), current_block_(0), - current_offset_(0) { + : initial_block_size_(initial_size), first_block_(nullptr), + current_block_(nullptr), current_offset_(0) { add_block(initial_size); } - ~ArenaAllocator() = default; + ~ArenaAllocator() { + Block *current = first_block_; + while (current) { + Block *next = current->next; + std::free(current); + current = next; + } + } ArenaAllocator(const ArenaAllocator &) = delete; ArenaAllocator &operator=(const ArenaAllocator &) = delete; - ArenaAllocator(ArenaAllocator &&) = default; - ArenaAllocator &operator=(ArenaAllocator &&) = default; + ArenaAllocator(ArenaAllocator &&other) noexcept + : initial_block_size_(other.initial_block_size_), + first_block_(other.first_block_), current_block_(other.current_block_), + current_offset_(other.current_offset_) { + other.first_block_ = nullptr; + other.current_block_ = nullptr; + other.current_offset_ = 0; + } + + ArenaAllocator &operator=(ArenaAllocator &&other) noexcept { + if (this != &other) { + Block *current = first_block_; + while (current) { + Block *next = current->next; + std::free(current); + current = next; + } + + initial_block_size_ = other.initial_block_size_; + first_block_ = other.first_block_; + current_block_ = other.current_block_; + current_offset_ = other.current_offset_; + + other.first_block_ = nullptr; + other.current_block_ = nullptr; + other.current_offset_ = 0; + } + return *this; + } void *allocate(size_t size, size_t alignment = alignof(std::max_align_t)) { if (size == 0) { return nullptr; } - char *block_start = blocks_[current_block_].get(); + if (!current_block_) { + return nullptr; + } + + char *block_start = current_block_->data(); uintptr_t block_addr = reinterpret_cast(block_start); size_t aligned_offset = align_up(block_addr + current_offset_, alignment) - block_addr; - if (aligned_offset + size > block_sizes_[current_block_]) { + if (aligned_offset + size > current_block_->size) { size_t next_block_size = calculate_next_block_size(size); add_block(next_block_size); - block_start = blocks_[current_block_].get(); + block_start = current_block_->data(); block_addr = reinterpret_cast(block_start); aligned_offset = align_up(block_addr, alignment) - block_addr; } @@ -50,43 +106,65 @@ public: } void reset() { - current_block_ = 0; + current_block_ = first_block_; current_offset_ = 0; } size_t total_allocated() const { size_t total = 0; - for (size_t size : block_sizes_) { - total += size; + Block *current = first_block_; + while (current) { + total += current->size; + current = current->next; } return total; } size_t used_bytes() const { size_t total = current_offset_; - for (size_t i = 0; i < current_block_; ++i) { - total += block_sizes_[i]; + Block *current = first_block_; + while (current && current != current_block_) { + total += current->size; + current = current->next; } return total; } size_t available_in_current_block() const { - return block_sizes_[current_block_] - current_offset_; + return current_block_ ? current_block_->size - current_offset_ : 0; } - size_t num_blocks() const { return blocks_.size(); } + size_t num_blocks() const { + size_t count = 0; + Block *current = first_block_; + while (current) { + count++; + current = current->next; + } + return count; + } private: void add_block(size_t size) { - blocks_.emplace_back(std::make_unique(size)); - block_sizes_.push_back(size); - current_block_ = blocks_.size() - 1; + Block *new_block = Block::create(size); + + if (!first_block_) { + first_block_ = new_block; + } else { + Block *current = first_block_; + while (current->next) { + current = current->next; + } + current->next = new_block; + } + + current_block_ = new_block; current_offset_ = 0; } size_t calculate_next_block_size(size_t required_size) const { size_t current_size = - blocks_.empty() ? initial_block_size_ : block_sizes_[current_block_]; + current_block_ ? current_block_->size : initial_block_size_; size_t doubled_size = current_size * 2; return std::max(required_size, doubled_size); @@ -100,8 +178,7 @@ private: } size_t initial_block_size_; - size_t current_block_; + Block *first_block_; + Block *current_block_; size_t current_offset_; - std::vector> blocks_; - std::vector block_sizes_; };