#pragma once #include #include #include #include #include #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 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 struct rebind { typedef BlockAllocator 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 BlockAllocator(BlockAllocator const& other); BlockAllocator& operator=(BlockAllocator const& rhs) = default; BlockAllocator& operator=(BlockAllocator&& rhs) = default; // If n is != 1, will fall back on std::allocator T* allocate(size_t n); void deallocate(T* p, size_t n); template void construct(pointer p, Args&&... args) const; void destroy(pointer p) const; // BlockAllocator will always be != to any other BlockAllocator instance template bool operator==(BlockAllocator const& rhs) const; template bool operator!=(BlockAllocator const& rhs) const; private: template friend class BlockAllocator; using ChunkIndex = std::conditional_t::max(), uint8_t, std::conditional_t::max(), uint16_t, std::conditional_t::max(), uint32_t, std::conditional_t::max(), uint64_t, uintmax_t>>>>; static ChunkIndex const NullChunkIndex = std::numeric_limits::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 chunks; ChunkIndex firstUnallocated = NullChunkIndex; ChunkIndex allocationCount = 0; }; struct Data { std::vector> blocks; Block* unfilledBlock; std::allocator multiAllocator; }; typedef std::unordered_map> BlockAllocatorFamily; static Data* getAllocatorData(BlockAllocatorFamily& family); shared_ptr m_family; Data* m_data; }; template BlockAllocator::BlockAllocator() { m_family = make_shared(); m_data = getAllocatorData(*m_family); m_data->blocks.reserve(32); m_data->unfilledBlock = nullptr; } template template BlockAllocator::BlockAllocator(BlockAllocator const& other) : m_family(other.m_family) { m_data = getAllocatorData(*m_family); } template T* BlockAllocator::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(); m_data->unfilledBlock = block.get(); auto sortedPosition = std::lower_bound(m_data->blocks.begin(), m_data->blocks.end(), block.get(), [](std::unique_ptr const& a, Block* b) { return a.get() < b; }); m_data->blocks.insert(sortedPosition, std::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 void BlockAllocator::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 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 template void BlockAllocator::construct(pointer p, Args&&... args) const { new (p) T(std::forward(args)...); } template void BlockAllocator::destroy(pointer p) const { p->~T(); } template template bool BlockAllocator::operator==(BlockAllocator const& rhs) const { return m_family == rhs.m_family; } template template bool BlockAllocator::operator!=(BlockAllocator const& rhs) const { return m_family != rhs.m_family; } template T* BlockAllocator::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 void BlockAllocator::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 bool BlockAllocator::Block::full() const { return allocationCount == BlockSize; } template bool BlockAllocator::Block::empty() const { return allocationCount == 0; } template auto BlockAllocator::Block::chunkPointer(ChunkIndex chunkIndex) -> Chunk* { starAssert(chunkIndex < BlockSize); return &chunks[chunkIndex]; } template typename BlockAllocator::Data* BlockAllocator::getAllocatorData(BlockAllocatorFamily& family) { auto& dataptr = family[typeid(Data)]; if (!dataptr) dataptr = make_shared(); return (Data*)dataptr.get(); } }