Intrusive linked list for arena allocator
This commit is contained in:
@@ -1,39 +1,95 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cstdlib>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <new>
|
||||||
|
|
||||||
class ArenaAllocator {
|
class ArenaAllocator {
|
||||||
|
private:
|
||||||
|
struct Block {
|
||||||
|
size_t size;
|
||||||
|
Block *next;
|
||||||
|
|
||||||
|
char *data() { return reinterpret_cast<char *>(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:
|
public:
|
||||||
explicit ArenaAllocator(size_t initial_size = 1024)
|
explicit ArenaAllocator(size_t initial_size = 1024)
|
||||||
: initial_block_size_(initial_size), current_block_(0),
|
: initial_block_size_(initial_size), first_block_(nullptr),
|
||||||
current_offset_(0) {
|
current_block_(nullptr), current_offset_(0) {
|
||||||
add_block(initial_size);
|
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(const ArenaAllocator &) = delete;
|
||||||
ArenaAllocator &operator=(const ArenaAllocator &) = delete;
|
ArenaAllocator &operator=(const ArenaAllocator &) = delete;
|
||||||
|
|
||||||
ArenaAllocator(ArenaAllocator &&) = default;
|
ArenaAllocator(ArenaAllocator &&other) noexcept
|
||||||
ArenaAllocator &operator=(ArenaAllocator &&) = default;
|
: 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)) {
|
void *allocate(size_t size, size_t alignment = alignof(std::max_align_t)) {
|
||||||
if (size == 0) {
|
if (size == 0) {
|
||||||
return nullptr;
|
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<uintptr_t>(block_start);
|
uintptr_t block_addr = reinterpret_cast<uintptr_t>(block_start);
|
||||||
size_t aligned_offset =
|
size_t aligned_offset =
|
||||||
align_up(block_addr + current_offset_, alignment) - block_addr;
|
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);
|
size_t next_block_size = calculate_next_block_size(size);
|
||||||
add_block(next_block_size);
|
add_block(next_block_size);
|
||||||
block_start = blocks_[current_block_].get();
|
block_start = current_block_->data();
|
||||||
block_addr = reinterpret_cast<uintptr_t>(block_start);
|
block_addr = reinterpret_cast<uintptr_t>(block_start);
|
||||||
aligned_offset = align_up(block_addr, alignment) - block_addr;
|
aligned_offset = align_up(block_addr, alignment) - block_addr;
|
||||||
}
|
}
|
||||||
@@ -50,43 +106,65 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
current_block_ = 0;
|
current_block_ = first_block_;
|
||||||
current_offset_ = 0;
|
current_offset_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t total_allocated() const {
|
size_t total_allocated() const {
|
||||||
size_t total = 0;
|
size_t total = 0;
|
||||||
for (size_t size : block_sizes_) {
|
Block *current = first_block_;
|
||||||
total += size;
|
while (current) {
|
||||||
|
total += current->size;
|
||||||
|
current = current->next;
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t used_bytes() const {
|
size_t used_bytes() const {
|
||||||
size_t total = current_offset_;
|
size_t total = current_offset_;
|
||||||
for (size_t i = 0; i < current_block_; ++i) {
|
Block *current = first_block_;
|
||||||
total += block_sizes_[i];
|
while (current && current != current_block_) {
|
||||||
|
total += current->size;
|
||||||
|
current = current->next;
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t available_in_current_block() const {
|
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:
|
private:
|
||||||
void add_block(size_t size) {
|
void add_block(size_t size) {
|
||||||
blocks_.emplace_back(std::make_unique<char[]>(size));
|
Block *new_block = Block::create(size);
|
||||||
block_sizes_.push_back(size);
|
|
||||||
current_block_ = blocks_.size() - 1;
|
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;
|
current_offset_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t calculate_next_block_size(size_t required_size) const {
|
size_t calculate_next_block_size(size_t required_size) const {
|
||||||
size_t current_size =
|
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;
|
size_t doubled_size = current_size * 2;
|
||||||
|
|
||||||
return std::max(required_size, doubled_size);
|
return std::max(required_size, doubled_size);
|
||||||
@@ -100,8 +178,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t initial_block_size_;
|
size_t initial_block_size_;
|
||||||
size_t current_block_;
|
Block *first_block_;
|
||||||
|
Block *current_block_;
|
||||||
size_t current_offset_;
|
size_t current_offset_;
|
||||||
std::vector<std::unique_ptr<char[]>> blocks_;
|
|
||||||
std::vector<size_t> block_sizes_;
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user