Skip to content

Instantly share code, notes, and snippets.

@SeijiEmery
Last active March 15, 2017 17:32
Show Gist options
  • Select an option

  • Save SeijiEmery/af6ba2ed42884ece06c2e57f0f57a748 to your computer and use it in GitHub Desktop.

Select an option

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)
// 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