diff options
Diffstat (limited to 'source/core/StarBlockAllocator.hpp')
-rw-r--r-- | source/core/StarBlockAllocator.hpp | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/source/core/StarBlockAllocator.hpp b/source/core/StarBlockAllocator.hpp new file mode 100644 index 0000000..5051e66 --- /dev/null +++ b/source/core/StarBlockAllocator.hpp @@ -0,0 +1,268 @@ +#ifndef STAR_BLOCK_ALLOCATOR_HPP +#define STAR_BLOCK_ALLOCATOR_HPP + +#include <array> +#include <vector> +#include <unordered_map> +#include <limits> +#include <typeindex> + +#include "StarException.hpp" + +namespace Star { + +// Constant size only allocator using fixed size blocks of memory. much faster +// than general purpose allocators, but not thread safe. Useful as the +// allocator for containers that mostly allocate one element at a time, such as +// std::list, std::map, std::set etc. +template <typename T, size_t BlockSize> +class BlockAllocator { +public: + typedef T value_type; + + typedef T* pointer; + typedef T const* const_pointer; + + typedef T& reference; + typedef T const& const_reference; + + // Allocator can be shared, but since it is NOT thread safe this should not + // be done by default. + typedef std::false_type propagate_on_container_copy_assignment; + typedef std::true_type propagate_on_container_move_assignment; + typedef std::true_type propagate_on_container_swap; + + template <class U> + struct rebind { + typedef BlockAllocator<U, BlockSize> other; + }; + + BlockAllocator(); + // Copy constructed BlockAllocators of the same type share underlying + // resources. + BlockAllocator(BlockAllocator const& other) = default; + BlockAllocator(BlockAllocator&& other) = default; + // Copy constructed BlockAllocators of different type share no resources + template <class U> + BlockAllocator(BlockAllocator<U, BlockSize> const& other); + + BlockAllocator& operator=(BlockAllocator const& rhs) = default; + BlockAllocator& operator=(BlockAllocator&& rhs) = default; + + // If n is != 1, will fall back on std::allocator<T> + T* allocate(size_t n); + void deallocate(T* p, size_t n); + + template <typename... Args> + void construct(pointer p, Args&&... args) const; + void destroy(pointer p) const; + + // BlockAllocator will always be != to any other BlockAllocator instance + template <class U> + bool operator==(BlockAllocator<U, BlockSize> const& rhs) const; + template <class U> + bool operator!=(BlockAllocator<U, BlockSize> const& rhs) const; + +private: + template <typename OtherT, size_t OtherBlockSize> + friend class BlockAllocator; + + using ChunkIndex = + std::conditional_t<BlockSize <= std::numeric_limits<uint8_t>::max(), uint8_t, + std::conditional_t<BlockSize <= std::numeric_limits<uint16_t>::max(), uint16_t, + std::conditional_t<BlockSize <= std::numeric_limits<uint32_t>::max(), uint32_t, + std::conditional_t<BlockSize <= std::numeric_limits<uint64_t>::max(), uint64_t, uintmax_t>>>>; + + static ChunkIndex const NullChunkIndex = std::numeric_limits<ChunkIndex>::max(); + + struct Unallocated { + ChunkIndex prev; + ChunkIndex next; + }; + + typedef std::aligned_union_t<0, T, Unallocated> Chunk; + + struct Block { + T* allocate(); + void deallocate(T* ptr); + + bool full() const; + bool empty() const; + + Chunk* chunkPointer(ChunkIndex chunkIndex); + + std::array<Chunk, BlockSize> chunks; + ChunkIndex firstUnallocated = NullChunkIndex; + ChunkIndex allocationCount = 0; + }; + + struct Data { + std::vector<unique_ptr<Block>> blocks; + Block* unfilledBlock; + std::allocator<T> multiAllocator; + }; + + typedef std::unordered_map<std::type_index, shared_ptr<void>> BlockAllocatorFamily; + + static Data* getAllocatorData(BlockAllocatorFamily& family); + + shared_ptr<BlockAllocatorFamily> m_family; + Data* m_data; +}; + +template <typename T, size_t BlockSize> +BlockAllocator<T, BlockSize>::BlockAllocator() { + m_family = make_shared<BlockAllocatorFamily>(); + m_data = getAllocatorData(*m_family); + m_data->blocks.reserve(32); + m_data->unfilledBlock = nullptr; +} + +template <typename T, size_t BlockSize> +template <class U> +BlockAllocator<T, BlockSize>::BlockAllocator(BlockAllocator<U, BlockSize> const& other) + : m_family(other.m_family) { + m_data = getAllocatorData(*m_family); +} + +template <typename T, size_t BlockSize> +T* BlockAllocator<T, BlockSize>::allocate(size_t n) { + if (n == 1) { + if (m_data->unfilledBlock == nullptr) { + for (auto const& p : m_data->blocks) { + if (!p->full()) { + m_data->unfilledBlock = p.get(); + break; + } + } + + if (!m_data->unfilledBlock) { + auto block = make_unique<Block>(); + m_data->unfilledBlock = block.get(); + auto sortedPosition = std::lower_bound(m_data->blocks.begin(), m_data->blocks.end(), block.get(), [](std::unique_ptr<Block> const& a, Block* b) { + return a.get() < b; + }); + m_data->blocks.insert(sortedPosition, move(block)); + } + } + + auto allocated = m_data->unfilledBlock->allocate(); + if (m_data->unfilledBlock->full()) + m_data->unfilledBlock = nullptr; + return allocated; + } else { + return m_data->multiAllocator.allocate(n); + } +} + +template <typename T, size_t BlockSize> +void BlockAllocator<T, BlockSize>::deallocate(T* p, size_t n) { + if (n == 1) { + starAssert(p); + + auto i = std::upper_bound(m_data->blocks.begin(), m_data->blocks.end(), p, [](T* a, std::unique_ptr<Block> const& b) { + return a < (T*)b->chunkPointer(0); + }); + + starAssert(i != m_data->blocks.begin()); + --i; + + (*i)->deallocate(p); + + if (!m_data->unfilledBlock) { + m_data->unfilledBlock = i->get(); + } else if ((*i)->empty()) { + if (m_data->unfilledBlock != i->get()) + m_data->blocks.erase(i); + } + } else { + m_data->multiAllocator.deallocate(p, n); + } +} + +template <typename T, size_t BlockSize> +template <typename... Args> +void BlockAllocator<T, BlockSize>::construct(pointer p, Args&&... args) const { + new (p) T(forward<Args>(args)...); +} + +template <typename T, size_t BlockSize> +void BlockAllocator<T, BlockSize>::destroy(pointer p) const { + p->~T(); +} + +template <typename T, size_t BlockSize> +template <class U> +bool BlockAllocator<T, BlockSize>::operator==(BlockAllocator<U, BlockSize> const& rhs) const { + return m_family == rhs.m_family; +} + +template <typename T, size_t BlockSize> +template <class U> +bool BlockAllocator<T, BlockSize>::operator!=(BlockAllocator<U, BlockSize> const& rhs) const { + return m_family != rhs.m_family; +} + +template <typename T, size_t BlockSize> +T* BlockAllocator<T, BlockSize>::Block::allocate() { + starAssert(allocationCount < BlockSize); + + T* allocated; + if (firstUnallocated == NullChunkIndex) { + allocated = (T*)chunkPointer(allocationCount); + } else { + void* chunk = chunkPointer(firstUnallocated); + starAssert(((Unallocated*)chunk)->prev == NullChunkIndex); + firstUnallocated = ((Unallocated*)chunk)->next; + if (firstUnallocated != NullChunkIndex) + ((Unallocated*)chunkPointer(firstUnallocated))->prev = NullChunkIndex; + allocated = (T*)chunk; + } + + ++allocationCount; + return allocated; +} + +template <typename T, size_t BlockSize> +void BlockAllocator<T, BlockSize>::Block::deallocate(T* ptr) { + starAssert(allocationCount > 0); + + ChunkIndex chunkIndex = ptr - (T*)chunkPointer(0); + starAssert((T*)chunkPointer(chunkIndex) == ptr); + + auto c = (Unallocated*)chunkPointer(chunkIndex); + c->prev = NullChunkIndex; + c->next = firstUnallocated; + if (firstUnallocated != NullChunkIndex) + ((Unallocated*)chunkPointer(firstUnallocated))->prev = chunkIndex; + firstUnallocated = chunkIndex; + --allocationCount; +} + +template <typename T, size_t BlockSize> +bool BlockAllocator<T, BlockSize>::Block::full() const { + return allocationCount == BlockSize; +} + +template <typename T, size_t BlockSize> +bool BlockAllocator<T, BlockSize>::Block::empty() const { + return allocationCount == 0; +} + +template <typename T, size_t BlockSize> +auto BlockAllocator<T, BlockSize>::Block::chunkPointer(ChunkIndex chunkIndex) -> Chunk* { + starAssert(chunkIndex < BlockSize); + return &chunks[chunkIndex]; +} + +template <typename T, size_t BlockSize> +typename BlockAllocator<T, BlockSize>::Data* BlockAllocator<T, BlockSize>::getAllocatorData(BlockAllocatorFamily& family) { + auto& dataptr = family[typeid(Data)]; + if (!dataptr) + dataptr = make_shared<Data>(); + return (Data*)dataptr.get(); +} + +} + +#endif |