Last active
March 15, 2017 17:32
-
-
Save SeijiEmery/af6ba2ed42884ece06c2e57f0f57a748 to your computer and use it in GitHub Desktop.
Pooled allocator example (for context, planning on using StackAllocator as memory backend for a polymorphic vtbl-based command buffer implementation)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Defines 2 allocators: PooledAllocator, an allocator-allocator, and StackAllocator, | |
| // a contiguous-region-polymorphic allocator that allocates + recycles chunks from PooledAllocator. | |
| // Note: these are both stateful, custom allocators, and do NOT conform to std::allocator. | |
| // Note: not tested; will try compiling later. Should work. | |
| template <typename Traits> | |
| class MemoryChunk : public Traits::Base { | |
| MemoryChunk () : data(traits::Allocator::malloc(Traits::size)) {} | |
| ~MemoryChunk () { traits::Allocator::free(data); } | |
| static constexpr size = Traits::size; | |
| void* const data; | |
| void clear () { memset(&data[0], size, 0); } | |
| }; | |
| template <typename Traits> | |
| class AllocatorPool { | |
| // Forwards AllocatorPool traits to MemoryChunk traits. | |
| struct MemoryChunkTraits { | |
| enum { size = Traits::chunk_size }; | |
| typedef Traits::BaseChunk Base; | |
| typedef Traits::Allocator Allocator; | |
| }; | |
| public: | |
| typedef MemoryChunk<MemoryChunkTraits> Chunk; | |
| typedef std::shared_ptr<Chunk> ChunkPtr; | |
| typedef std::shared_ptr<AllocatorPool> SharedPtr; | |
| protected: | |
| // Hidden constrtor; create with AllocatorPool::create() instead. | |
| AllocatorPool () {} | |
| public: | |
| virtual ~AllocatorPool () {} | |
| // Creates + returns a std::shared_ptr<AllocatorPool>. | |
| // Constructed this way so we can store a weak backreference to itself (the shared_ptr), | |
| // and thus pass a shared reference to itself to any child allocators. | |
| static SharedPtr create () { | |
| auto ptr = std::make_shared<AllocatorPool>(); | |
| ptr->thisPtr = std::weak_ptr<AllocatorPool>(ptr); | |
| return ptr; | |
| } | |
| // Create a std::shared_ptr of any child allocator type (eg. StackAllocator). | |
| // Only requirement is that the 1st argument to child ctor is an AllocatorPool::SharedPtr. | |
| template <typename Allocator> | |
| std::shared_ptr<Allocator> createSharedAllocator () { | |
| return std::make_shared<Allocator>(thisPtr.lock()); | |
| } | |
| // Create a std::unique_ptr of any child allocator type (eg. StackAllocator). | |
| // Only requirement is that the 1st argument to child ctor is an AllocatorPool::SharedPtr. | |
| template <typename Allocator> | |
| std::unique_ptr<Allocator> createUniqueAllocator () { | |
| return std::unique_ptr<Allocator>(new Allocator(thisPtr.lock())); | |
| } | |
| // Allocate an std::shared_ptr<AllocatorPool::Chunk>, to be used by child allocators. | |
| // Note: this method locks, to enable threadsafe chunk allocation. Chunks get recycled | |
| // automatically, as soon as their shared_ptr's use_count reaches 1 (ie. the only reference | |
| // to it is from the AllocatorPool itself). | |
| ChunkPtr allocChunk () { | |
| std::lock lock (mutex); | |
| for (auto& chunk : chunks) | |
| if (chunk.use_count() == 1) | |
| return chunk; | |
| auto chunk = std::make_shared<Chunk>(); | |
| chunks.push_back(chunk); | |
| return chunk; | |
| } | |
| private: | |
| std::vector<ChunkPtr> chunks; // List of owned, allocated fixed-size memory chunks. | |
| std::mutex mutex; // Mutex to lock chunk allocation. | |
| std::weak_ptr<AllocatorPool> thisPtr; // Backreference to self used when creating child allocators. | |
| }; | |
| struct DefaultStackAllocatorTraits { | |
| struct BaseChunk {}; | |
| enum { chunk_size = 1024 * 1024 * 16 }; | |
| enum { has_trivial_dtors = false }; | |
| typedef std::default_allocator Allocator; | |
| }; | |
| template <typename Traits = DefaultStackAllocatorTraits> | |
| class StackAllocator { | |
| private: | |
| struct detail { | |
| template <typename A, typename B, bool cond> | |
| struct Select; | |
| template <typename A, typename B> | |
| struct Select<A, B, true> { typedef A result }; | |
| template <typename A, typename B> | |
| struct Select<A, B, false> { typedef B result }; | |
| struct PODStorage { | |
| template <typename T> | |
| static constexpr size_t size () { return sizeof(T); } | |
| template <typename T, typename... Args> | |
| static T* create (void* mem, Args... args) { | |
| return new (mem) T(args...); | |
| } | |
| static void destructRegion (void* mem, size_t& size) { | |
| // Do nothing; dtors of created types will not get called. | |
| size = 0; | |
| } | |
| }; | |
| struct NonPODStorage { | |
| // Base interface for polymorphic storage type | |
| struct IPolymorphic { | |
| virtual void destroy () const = 0; // call dtor on stored object | |
| virtual size_t size () const = 0; // return sizeof(*this) | |
| }; | |
| // Simple polymorphic storage type used by chunk | |
| template <typename T> | |
| struct Polymorphic : public IPolymorphic { | |
| // Construct enclosed object w/ argument forwarding | |
| template <typename... Args> | |
| Polymorphic (Args... args) : object(args...) {} | |
| // Call dtor on stored object, get sizeof(*this), and get pointer to stored type. | |
| IPolymorphic* destroy () const override { object->~T(); } | |
| size_t size () const override { return sizeof(Polymorphic<T>); } | |
| T* get () { return &object; } | |
| private: | |
| T object; | |
| }; | |
| template <typename T> | |
| static constexpr size_t size () { return sizeof(Polymorphic<T>); } | |
| template <typename T, typename... Args> | |
| static T* create (void* mem, Args... args) { | |
| auto ptr = new (mem) Polymorphic<T>(args...); | |
| return ptr->get(); | |
| } | |
| static void destructRegion (void* mem, size_t& size) { | |
| IPolymorphic* ptr = reinterpret_cast<IPolymorphic*>(&chunk->data[0]); | |
| while (size) { | |
| size_t sz = ptr->size(); | |
| ptr->destroy(); | |
| size -= sz; | |
| assert(reinterpret_cast<ptrdiff_t>(size) >= 0); | |
| ptr = reinterpret_cast<IPolymorphic*>(static_cast<void*>(ptr) + sz); | |
| } | |
| } | |
| }; | |
| }; // struct detail | |
| // Select either PODStorage or NonPODStorage | |
| typedef detail::Select< | |
| detail::PODStorage, | |
| detail::NonPODStorage, | |
| Traits::has_trivial_dtors | |
| >::result Storage; | |
| typedef AllocatorPool<Traits> ChunkAllocator; | |
| struct Chunk { | |
| ChunkAllocator::ChunkPtr chunk; | |
| size_t offset = 0; | |
| Chunk (decltype(chunk) chunk) : chunk(std::move(chunk)) { chunk->clear(); } | |
| Chunk (const Chunk&) = delete; | |
| Chunk (Chunk&&) = default; | |
| Chunk& operator= (const Chunk&) = delete; | |
| Chunk& operator= (Chunk&&) = default; | |
| void* alloc (size_t size) { | |
| if (size + offset >= ChunkAllocator::Chunk::size) | |
| return nullptr; | |
| auto ptr = &chunk->data[offset]; | |
| offset += size; | |
| return ptr; | |
| } | |
| ~Chunk () { Storage::destructRegion(chunk, offset); } | |
| }; | |
| typedef std::vector<Chunk> ChunkList; | |
| void* alloc (size_t sz) { | |
| void* ptr = nullptr; | |
| while (chunks.empty() || (ptr = chunks.back().alloc(size)) == nullptr) | |
| chunks.emplace_back(pool->allocChunk()); | |
| return ptr; | |
| } | |
| public: | |
| // Construct a stack allocator | |
| StackAllocator (ChunkAllocator::SharedPtr pool) | |
| : pool(std::move(pool)) | |
| {} | |
| // Copy-construct, etc., prohibited. Use unique_ptr to pass around references to this allocator. | |
| StackAllocator (const StackAllocator&) = delete; | |
| StackAllocator& operator= (const StackAllocator&) = delete; | |
| // Automatic cleanup w/ default dtor: vector dtors chunks, which decreases ref count | |
| // on shared AllocatorPool::ChunkPtrs, which frees them from allocation elsewhere. | |
| // All memory freed when / if parent AllocatorPool gets dtor-ed; we keep a reference | |
| // to it so that it will not go out of scope within our lifetime | |
| virtual ~StackAllocator () {} | |
| // Allocates memory for + constructs an object of type T with forwarded arguments. | |
| template <typename T, typename... Args> | |
| T* construct (Args... args) { | |
| static_assert(Storage::size<T>() < ChunkAllocator::Chunk::size, | |
| "Data type too large to allocate within allocation chunk!"); | |
| return Storage::create<T>(alloc(Storage::size<T>()), args...); | |
| } | |
| private: | |
| ChunkAllocator::SharedPtr pool; | |
| std::vector<Chunk> chunks; | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment