From ba827a71ceac0cccaa5f5b110765e084aafccd75 Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Thu, 14 Aug 2025 12:02:48 -0400 Subject: [PATCH] Add documentation --- src/arena_allocator.hpp | 236 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 231 insertions(+), 5 deletions(-) diff --git a/src/arena_allocator.hpp b/src/arena_allocator.hpp index 4b55649..884c618 100644 --- a/src/arena_allocator.hpp +++ b/src/arena_allocator.hpp @@ -7,17 +7,86 @@ #include #include +/** + * @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(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 { 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 { - size_t size; - Block *prev; - size_t total_size; // Accumulated size of this block + all previous blocks - size_t block_count; // Number of blocks including this one + all previous - // blocks + size_t size; ///< Size of this block's data area + Block *prev; ///< Pointer to previous block (nullptr for first block) + size_t total_size; ///< Accumulated size of this block + all previous blocks + size_t block_count; ///< Number of blocks including this one + all previous + ///< 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(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) { void *memory = std::aligned_alloc(alignof(Block), sizeof(Block) + size); if (!memory) { @@ -31,10 +100,25 @@ private: }; 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) : initial_block_size_(initial_size), current_block_(nullptr), 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() { while (current_block_) { 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; + /// Copy assignment is not allowed (would be expensive and error-prone) 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 : initial_block_size_(other.initial_block_size_), current_block_(other.current_block_), @@ -54,6 +144,15 @@ public: 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 { if (this != &other) { while (current_block_) { @@ -72,6 +171,31 @@ public: 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( + * arena.allocate(sizeof(MyStruct), alignof(MyStruct))); + * ``` + */ void *allocate(size_t size, size_t alignment = alignof(std::max_align_t)) { if (size == 0) { return nullptr; @@ -101,11 +225,55 @@ public: 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("Hello, World!"); + * MyClass* obj = arena.construct(arg1, arg2, arg3); + * ``` + * + * ## Note: + * Objects constructed this way cannot be individually destroyed. + * Their destructors will NOT be called automatically. + */ template T *construct(Args &&...args) { void *ptr = allocate(sizeof(T), alignof(T)); return new (ptr) T(std::forward(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() { if (!current_block_) { return; @@ -135,10 +303,25 @@ public: 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 { 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 { if (!current_block_) { return 0; @@ -148,21 +331,51 @@ public: 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 { 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 { return current_block_ ? current_block_->block_count : 0; } 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) { Block *new_block = Block::create(size, current_block_); current_block_ = new_block; 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 current_size = current_block_ ? current_block_->size : initial_block_size_; @@ -171,6 +384,16 @@ private: 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) { if (alignment == 0 || (alignment & (alignment - 1)) != 0) { return value; @@ -178,7 +401,10 @@ private: return (value + alignment - 1) & ~(alignment - 1); } + /// Size used for the first block and baseline for geometric growth size_t initial_block_size_; + /// Pointer to the current (most recent) block, or nullptr if no blocks exist Block *current_block_; + /// Current offset within the current block's data area size_t current_offset_; };