arena_allocator.hpp

Back to Arena allocator

pastel/sys/allocator/arena_allocator/

#ifndef PASTELSYS_ARENA_ALLOCATOR_HPP
#define PASTELSYS_ARENA_ALLOCATOR_HPP

#include "pastel/sys/allocator/arena_allocator.h"
#include "pastel/sys/ensure.h"

namespace Pastel
{

    inline ArenaAllocator::ArenaAllocator(
        integer unitSize, integer blockSize)
        : blocks_()
        , unitsAllocated_(0)
        , unitsReserved_(0)
        , unitSize_(unitSize)
        , blockSize_(blockSize)
    {
        ENSURE_OP(unitSize, >=, 1);
        ENSURE_OP(blockSize, >=, 1);
    }

    inline ArenaAllocator::~ArenaAllocator()
    {
        REPORT1(unitsAllocated_ != 0, unitsAllocated_);

        clear();
    }

    inline void ArenaAllocator::swap(ArenaAllocator& that)
    {
        blocks_.swap(that.blocks_);
        std::swap(unitsAllocated_, that.unitsAllocated_);
        std::swap(unitsReserved_, that.unitsReserved_);
        std::swap(unitSize_, that.unitSize_);
        std::swap(blockSize_, that.blockSize_);
    }

    inline void ArenaAllocator::clear()
    {
        // For every block..

        Iterator iter(blocks_.begin());
        Iterator iterEnd(blocks_.end());

        while (iter != iterEnd)
        {
            // ..Deallocate the block..

            deallocateBlock(&*iter);
            ++iter;
        }

        // And clear the list of blocks.

        blocks_.clear();

        ASSERT1(unitsReserved_ == 0, unitsReserved_);

        unitsAllocated_ = 0;
        unitsReserved_ = 0;
    }

    inline integer ArenaAllocator::unitSize() const
    {
        return unitSize_;
    }

    inline integer ArenaAllocator::allocated() const
    {
        return unitsAllocated_;
    }

    inline integer ArenaAllocator::capacity() const
    {
        return unitsReserved_;
    }

    inline void* ArenaAllocator::allocate(integer units)
    {
        ENSURE_OP(units, >, 0);

        Block* block = 0;
        if (!blocks_.empty())
        {
            block = &blocks_.back();
        }

        if (!block ||
            block->unitsReserved_ - block->unitsAllocated_ < units)
        {
            // Not enough space on this block,
            // allocate another one.
            allocateBlock(units);
            block = &blocks_.back();
        }

        // Calculate the memory address of the first
        // free unit inside the block.
        integer firstFreeIndex = block->unitsAllocated_;

        uint8* const memAddress = block->data_ + firstFreeIndex * unitSize_;

        // Keep up book-keeping.
        block->unitsAllocated_ += units;
        unitsAllocated_ += units;

        // Return the allocated memory address.
        return (void*)memAddress;
    }

    inline void ArenaAllocator::deallocate(const void* memoryIgnored, integer units)
    {
        unused(memoryIgnored);

        unitsAllocated_ -= units;
        REPORT1(unitsAllocated_ < 0, unitsAllocated_);
    }

    // Private

    inline void ArenaAllocator::allocateBlock(integer minUnits)
    {
        ASSERT1(minUnits >= 0, minUnits);

        // The block must be at least of this size.
        integer blockSize = minUnits;

        // Requests larger than or equal to blockSize_ 
        // are satisfied exactly.
        if (blockSize < blockSize_)
        {
            // Otherwise, we seek to possibly allocate 
            // a larger block.

            // The block size must be at least 16.

            integer MinBlockSize = 16;
            blockSize = std::max(blockSize, MinBlockSize);

            // Choose the block size by an 1.5-exponential rule,
            // if it makes the block larger. This gives blocks
            // smoothly increasing sizes at the beginning,
            // rather than allocating 'blockSize_' blocks right
            // from the start. The intent here is to save
            // memory when only a small number of elements
            // is needed.

            blockSize = std::max(unitsAllocated_ >> 1, blockSize);

            // But limit that exponential rule so that it
            // does not exceed blockSize_. 

            blockSize = std::min(blockSize, blockSize_);
        }

        // Allocate and initialize a new
        // block.
        Block block;

        block.data_= (uint8*)allocateRaw(blockSize * unitSize_);
        block.unitsAllocated_ = 0;
        block.unitsReserved_ = blockSize;

        try
        {
            blocks_.push_back(block);
        }
        catch(...)
        {
            deallocateRaw((void*)block.data_);
            throw;
        }

        // Keep up the book-keeping.
        unitsReserved_ += blockSize;
    }

    inline void ArenaAllocator::deallocateBlock(
        Block* block)
    {
        ASSERT(block);

        // Keep up book-keeping.
        unitsReserved_ -= block->unitsReserved_;

        // Delete block's raw memory.
        deallocateRaw((void*)block->data_);
    }

    inline void swap(
        ArenaAllocator& left,
        ArenaAllocator& right)
    {
        left.swap(right);
    }

}

#endif