Add documentation
This commit is contained in:
@@ -7,17 +7,86 @@
|
|||||||
#include <new>
|
#include <new>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A high-performance arena allocator for bulk allocations.
|
||||||
|
*
|
||||||
|
* ArenaAllocator provides extremely fast memory allocation (~1ns per
|
||||||
|
* allocation) by allocating large blocks and serving allocations from them
|
||||||
|
* sequentially. It's designed for scenarios where many small objects need to be
|
||||||
|
* allocated and can all be deallocated together.
|
||||||
|
*
|
||||||
|
* ## Key Features:
|
||||||
|
* - **Ultra-fast allocation**: ~1ns per allocation vs ~20-270ns for malloc
|
||||||
|
* - **Lazy initialization**: No memory allocated until first use
|
||||||
|
* - **Intrusive linked list**: Minimal memory overhead using backward-linked
|
||||||
|
* blocks
|
||||||
|
* - **Geometric growth**: Block sizes double to minimize allocations
|
||||||
|
* - **Memory efficient reset**: Frees unused blocks to prevent memory leaks
|
||||||
|
* - **Proper alignment**: Respects alignment requirements for all types
|
||||||
|
*
|
||||||
|
* ## Performance Characteristics:
|
||||||
|
* - Allocation: O(1) amortized
|
||||||
|
* - Memory tracking: O(1) using accumulated counters
|
||||||
|
* - Reset: O(n) where n is number of blocks (but frees memory)
|
||||||
|
* - Destruction: O(n) where n is number of blocks
|
||||||
|
*
|
||||||
|
* ## Usage Examples:
|
||||||
|
* ```cpp
|
||||||
|
* // Basic allocation
|
||||||
|
* ArenaAllocator arena(1024);
|
||||||
|
* void* ptr = arena.allocate(100);
|
||||||
|
*
|
||||||
|
* // Construct objects in-place
|
||||||
|
* MyClass* obj = arena.construct<MyClass>(arg1, arg2);
|
||||||
|
*
|
||||||
|
* // Track memory usage
|
||||||
|
* size_t total = arena.total_allocated();
|
||||||
|
* size_t used = arena.used_bytes();
|
||||||
|
*
|
||||||
|
* // Reset to reuse first block (frees others)
|
||||||
|
* arena.reset();
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ## Memory Management:
|
||||||
|
* - Individual objects cannot be freed (by design)
|
||||||
|
* - All memory is freed when the allocator is destroyed
|
||||||
|
* - reset() frees all blocks except the first one
|
||||||
|
* - Move semantics transfer ownership of all blocks
|
||||||
|
*
|
||||||
|
* ## Thread Safety:
|
||||||
|
* Not thread-safe. Use separate instances per thread or external
|
||||||
|
* synchronization.
|
||||||
|
*/
|
||||||
class ArenaAllocator {
|
class ArenaAllocator {
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Internal block structure for the intrusive linked list.
|
||||||
|
*
|
||||||
|
* Each block contains:
|
||||||
|
* - The actual data storage immediately following the Block header
|
||||||
|
* - Backward pointer to previous block (intrusive linked list)
|
||||||
|
* - Accumulated counters for O(1) tracking operations
|
||||||
|
*/
|
||||||
struct Block {
|
struct Block {
|
||||||
size_t size;
|
size_t size; ///< Size of this block's data area
|
||||||
Block *prev;
|
Block *prev; ///< Pointer to previous block (nullptr for first block)
|
||||||
size_t total_size; // Accumulated size of this block + all previous blocks
|
size_t total_size; ///< Accumulated size of this block + all previous blocks
|
||||||
size_t block_count; // Number of blocks including this one + all previous
|
size_t block_count; ///< Number of blocks including this one + all previous
|
||||||
// blocks
|
///< blocks
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get pointer to the data area of this block.
|
||||||
|
* @return Pointer to the start of the data area (after Block header).
|
||||||
|
*/
|
||||||
char *data() { return reinterpret_cast<char *>(this + 1); }
|
char *data() { return reinterpret_cast<char *>(this + 1); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a new block with the specified size.
|
||||||
|
* @param size Size of the data area for this block
|
||||||
|
* @param prev Pointer to the previous block (nullptr for first block)
|
||||||
|
* @return Pointer to the newly created block
|
||||||
|
* @throws std::bad_alloc if memory allocation fails
|
||||||
|
*/
|
||||||
static Block *create(size_t size, Block *prev) {
|
static Block *create(size_t size, Block *prev) {
|
||||||
void *memory = std::aligned_alloc(alignof(Block), sizeof(Block) + size);
|
void *memory = std::aligned_alloc(alignof(Block), sizeof(Block) + size);
|
||||||
if (!memory) {
|
if (!memory) {
|
||||||
@@ -31,10 +100,25 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct an ArenaAllocator with the specified initial block size.
|
||||||
|
*
|
||||||
|
* No memory is allocated until the first allocation request (lazy
|
||||||
|
* initialization). The initial block size is used for the first block and as
|
||||||
|
* the baseline for geometric growth.
|
||||||
|
*
|
||||||
|
* @param initial_size Size in bytes for the first block (default: 1024)
|
||||||
|
*/
|
||||||
explicit ArenaAllocator(size_t initial_size = 1024)
|
explicit ArenaAllocator(size_t initial_size = 1024)
|
||||||
: initial_block_size_(initial_size), current_block_(nullptr),
|
: initial_block_size_(initial_size), current_block_(nullptr),
|
||||||
current_offset_(0) {}
|
current_offset_(0) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destructor - frees all allocated blocks.
|
||||||
|
*
|
||||||
|
* Traverses the intrusive linked list backwards from current_block_,
|
||||||
|
* freeing each block. This ensures no memory leaks.
|
||||||
|
*/
|
||||||
~ArenaAllocator() {
|
~ArenaAllocator() {
|
||||||
while (current_block_) {
|
while (current_block_) {
|
||||||
Block *prev = current_block_->prev;
|
Block *prev = current_block_->prev;
|
||||||
@@ -43,9 +127,15 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Copy construction is not allowed (would be expensive and error-prone)
|
||||||
ArenaAllocator(const ArenaAllocator &) = delete;
|
ArenaAllocator(const ArenaAllocator &) = delete;
|
||||||
|
/// Copy assignment is not allowed (would be expensive and error-prone)
|
||||||
ArenaAllocator &operator=(const ArenaAllocator &) = delete;
|
ArenaAllocator &operator=(const ArenaAllocator &) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor - transfers ownership of all blocks.
|
||||||
|
* @param other The ArenaAllocator to move from (will be left empty)
|
||||||
|
*/
|
||||||
ArenaAllocator(ArenaAllocator &&other) noexcept
|
ArenaAllocator(ArenaAllocator &&other) noexcept
|
||||||
: initial_block_size_(other.initial_block_size_),
|
: initial_block_size_(other.initial_block_size_),
|
||||||
current_block_(other.current_block_),
|
current_block_(other.current_block_),
|
||||||
@@ -54,6 +144,15 @@ public:
|
|||||||
other.current_offset_ = 0;
|
other.current_offset_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move assignment operator - transfers ownership of all blocks.
|
||||||
|
*
|
||||||
|
* Frees any existing blocks in this allocator before taking ownership
|
||||||
|
* of blocks from the other allocator.
|
||||||
|
*
|
||||||
|
* @param other The ArenaAllocator to move from (will be left empty)
|
||||||
|
* @return Reference to this allocator
|
||||||
|
*/
|
||||||
ArenaAllocator &operator=(ArenaAllocator &&other) noexcept {
|
ArenaAllocator &operator=(ArenaAllocator &&other) noexcept {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
while (current_block_) {
|
while (current_block_) {
|
||||||
@@ -72,6 +171,31 @@ public:
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocate memory with the specified size and alignment.
|
||||||
|
*
|
||||||
|
* This is the core allocation method providing ~1ns allocation performance.
|
||||||
|
* It performs lazy initialization on first use and automatically grows
|
||||||
|
* the arena when needed using geometric growth (doubling block sizes).
|
||||||
|
*
|
||||||
|
* @param size Number of bytes to allocate (0 returns nullptr)
|
||||||
|
* @param alignment Required alignment (default: alignof(std::max_align_t))
|
||||||
|
* @return Pointer to allocated memory, or nullptr if size is 0
|
||||||
|
* @throws std::bad_alloc if memory allocation fails
|
||||||
|
*
|
||||||
|
* ## Performance:
|
||||||
|
* - O(1) amortized allocation time
|
||||||
|
* - Respects alignment requirements with minimal padding
|
||||||
|
* - Automatically creates new blocks when current block is exhausted
|
||||||
|
*
|
||||||
|
* ## Example:
|
||||||
|
* ```cpp
|
||||||
|
* void* ptr1 = arena.allocate(100); // Default alignment
|
||||||
|
* void* ptr2 = arena.allocate(64, 16); // 16-byte aligned
|
||||||
|
* MyStruct* ptr3 = static_cast<MyStruct*>(
|
||||||
|
* arena.allocate(sizeof(MyStruct), alignof(MyStruct)));
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
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;
|
||||||
@@ -101,11 +225,55 @@ public:
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct an object of type T in the arena using placement new.
|
||||||
|
*
|
||||||
|
* This is a convenience method that combines allocation with in-place
|
||||||
|
* construction. It properly handles alignment requirements for type T.
|
||||||
|
*
|
||||||
|
* @tparam T The type of object to construct
|
||||||
|
* @tparam Args Types of constructor arguments
|
||||||
|
* @param args Arguments to forward to T's constructor
|
||||||
|
* @return Pointer to the constructed object
|
||||||
|
* @throws std::bad_alloc if memory allocation fails
|
||||||
|
*
|
||||||
|
* ## Example:
|
||||||
|
* ```cpp
|
||||||
|
* std::string* str = arena.construct<std::string>("Hello, World!");
|
||||||
|
* MyClass* obj = arena.construct<MyClass>(arg1, arg2, arg3);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ## Note:
|
||||||
|
* Objects constructed this way cannot be individually destroyed.
|
||||||
|
* Their destructors will NOT be called automatically.
|
||||||
|
*/
|
||||||
template <typename T, typename... Args> T *construct(Args &&...args) {
|
template <typename T, typename... Args> T *construct(Args &&...args) {
|
||||||
void *ptr = allocate(sizeof(T), alignof(T));
|
void *ptr = allocate(sizeof(T), alignof(T));
|
||||||
return new (ptr) T(std::forward<Args>(args)...);
|
return new (ptr) T(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the allocator to reuse the first block, freeing all others.
|
||||||
|
*
|
||||||
|
* This method provides memory-efficient reset behavior by:
|
||||||
|
* 1. Keeping the first block for reuse
|
||||||
|
* 2. Freeing all subsequent blocks to prevent memory leaks
|
||||||
|
* 3. Resetting allocation position to the start of the first block
|
||||||
|
*
|
||||||
|
* If no blocks have been allocated yet, this is a no-op.
|
||||||
|
*
|
||||||
|
* ## Performance:
|
||||||
|
* - O(n) where n is the number of blocks to free
|
||||||
|
* - Prevents memory leaks by freeing unused blocks
|
||||||
|
* - Faster than destroying and recreating the allocator
|
||||||
|
*
|
||||||
|
* ## Example:
|
||||||
|
* ```cpp
|
||||||
|
* arena.allocate(1000); // Creates blocks
|
||||||
|
* arena.reset(); // Frees extra blocks, keeps first
|
||||||
|
* arena.allocate(100); // Reuses first block
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
void reset() {
|
void reset() {
|
||||||
if (!current_block_) {
|
if (!current_block_) {
|
||||||
return;
|
return;
|
||||||
@@ -135,10 +303,25 @@ public:
|
|||||||
current_offset_ = 0;
|
current_offset_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the total number of bytes allocated across all blocks.
|
||||||
|
*
|
||||||
|
* Uses O(1) accumulated counters for fast retrieval.
|
||||||
|
*
|
||||||
|
* @return Total allocated bytes, or 0 if no blocks exist
|
||||||
|
*/
|
||||||
size_t total_allocated() const {
|
size_t total_allocated() const {
|
||||||
return current_block_ ? current_block_->total_size : 0;
|
return current_block_ ? current_block_->total_size : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the number of bytes currently used for allocations.
|
||||||
|
*
|
||||||
|
* This includes all fully used previous blocks plus the used portion
|
||||||
|
* of the current block. Uses O(1) accumulated counters.
|
||||||
|
*
|
||||||
|
* @return Number of bytes in use
|
||||||
|
*/
|
||||||
size_t used_bytes() const {
|
size_t used_bytes() const {
|
||||||
if (!current_block_) {
|
if (!current_block_) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -148,21 +331,51 @@ public:
|
|||||||
return prev_total + current_offset_;
|
return prev_total + current_offset_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the number of bytes available in the current block.
|
||||||
|
*
|
||||||
|
* @return Available bytes in current block, or 0 if no blocks exist
|
||||||
|
*/
|
||||||
size_t available_in_current_block() const {
|
size_t available_in_current_block() const {
|
||||||
return current_block_ ? current_block_->size - current_offset_ : 0;
|
return current_block_ ? current_block_->size - current_offset_ : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the total number of blocks in the allocator.
|
||||||
|
*
|
||||||
|
* Uses O(1) accumulated counters for fast retrieval.
|
||||||
|
*
|
||||||
|
* @return Number of blocks, or 0 if no blocks exist
|
||||||
|
*/
|
||||||
size_t num_blocks() const {
|
size_t num_blocks() const {
|
||||||
return current_block_ ? current_block_->block_count : 0;
|
return current_block_ ? current_block_->block_count : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Add a new block with the specified size to the allocator.
|
||||||
|
*
|
||||||
|
* Creates a new block and makes it the current block. Updates all
|
||||||
|
* accumulated counters automatically through Block::create().
|
||||||
|
*
|
||||||
|
* @param size Size of the data area for the new block
|
||||||
|
*/
|
||||||
void add_block(size_t size) {
|
void add_block(size_t size) {
|
||||||
Block *new_block = Block::create(size, current_block_);
|
Block *new_block = Block::create(size, current_block_);
|
||||||
current_block_ = new_block;
|
current_block_ = new_block;
|
||||||
current_offset_ = 0;
|
current_offset_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculate the size for the next block using geometric growth.
|
||||||
|
*
|
||||||
|
* Uses a doubling strategy to minimize the number of blocks while
|
||||||
|
* ensuring large allocations are handled efficiently.
|
||||||
|
*
|
||||||
|
* @param required_size Minimum size needed for the allocation
|
||||||
|
* @return Size for the next block (max of required_size and doubled current
|
||||||
|
* size)
|
||||||
|
*/
|
||||||
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 =
|
||||||
current_block_ ? current_block_->size : initial_block_size_;
|
current_block_ ? current_block_->size : initial_block_size_;
|
||||||
@@ -171,6 +384,16 @@ private:
|
|||||||
return std::max(required_size, doubled_size);
|
return std::max(required_size, doubled_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Align a value up to the specified alignment boundary.
|
||||||
|
*
|
||||||
|
* Uses bit manipulation for efficient alignment calculation.
|
||||||
|
* Only works with power-of-2 alignments.
|
||||||
|
*
|
||||||
|
* @param value The value to align
|
||||||
|
* @param alignment The alignment boundary (must be power of 2)
|
||||||
|
* @return The aligned value
|
||||||
|
*/
|
||||||
static size_t align_up(size_t value, size_t alignment) {
|
static size_t align_up(size_t value, size_t alignment) {
|
||||||
if (alignment == 0 || (alignment & (alignment - 1)) != 0) {
|
if (alignment == 0 || (alignment & (alignment - 1)) != 0) {
|
||||||
return value;
|
return value;
|
||||||
@@ -178,7 +401,10 @@ private:
|
|||||||
return (value + alignment - 1) & ~(alignment - 1);
|
return (value + alignment - 1) & ~(alignment - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Size used for the first block and baseline for geometric growth
|
||||||
size_t initial_block_size_;
|
size_t initial_block_size_;
|
||||||
|
/// Pointer to the current (most recent) block, or nullptr if no blocks exist
|
||||||
Block *current_block_;
|
Block *current_block_;
|
||||||
|
/// Current offset within the current block's data area
|
||||||
size_t current_offset_;
|
size_t current_offset_;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user