Skip to content

Instantly share code, notes, and snippets.

@Redchards
Last active January 3, 2016 15:50
Show Gist options
  • Select an option

  • Save Redchards/8add8d3e0ffb136ee05b to your computer and use it in GitHub Desktop.

Select an option

Save Redchards/8add8d3e0ffb136ee05b to your computer and use it in GitHub Desktop.
Extremly simple implementation of the Arena (fixed memory pool) allocator in C++14.
#ifndef ALLOCATOR
#define ALLOCATOR
#include <MetaMinimal.hxx>
#include <cstdlib>
#include <limits>
#include <type_traits>
#include <utility>
#define DEFAULT_ALIGN 1
template<class, class = void>
struct has_rebind : std::false_type {};
template<class Allocator>
struct has_rebind<Allocator, void_t<decltype(Allocator::template rebind<neutral>)>> : std::true_type {};
template<class, class = void>
struct has_size_type : std::false_type {};
template<class AllocationPolicy>
struct has_size_type<AllocationPolicy, void_t<decltype(typename AllocationPolicy::size_type{})>> : std::true_type {};
template<class, class = void>
struct has_difference_type : std::false_type {};
template<class AllocationPolicy>
struct has_difference_type<AllocationPolicy, void_t<decltype(typename AllocationPolicy::difference_type{})>> : std::true_type {};
template<template<class...> class AllocationPolicy, typename U, class... PolicyArgs>
struct rebind_allocator
{
typedef AllocationPolicy<U, PolicyArgs...> type;
};
template<template<class...> class AllocationPolicy, typename U, class ... PolicyArgs>
using rebind_allocator_t = typename rebind_allocator<AllocationPolicy, U, PolicyArgs...>::type;
/*
Very simple allocator creation helper.
*/
template<typename T,
template<class...> class AllocationPolicy,
class ... PolicyArgs>
class Allocator : public AllocationPolicy<T, PolicyArgs...>
{
template<class TAllocationPolicy, class = void>
struct get_alignment
: std::integral_constant<size_t, DEFAULT_ALIGN> {};
template<class TAllocationPolicy>
struct get_alignment<TAllocationPolicy, void_t<decltype(TAllocationPolicy::alignment)>>
: std::integral_constant<size_t, TAllocationPolicy::alignment> {};
public:
typedef AllocationPolicy<T, PolicyArgs...> Policy;
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef typename std::conditional<has_size_type<Policy>::value,
decltype(typename Policy::size_type{}),
size_t>::type size_type;
typedef typename std::conditional<has_difference_type<Policy>::value,
decltype(typename Policy::difference_type{}),
ptrdiff_t>::type difference_type;
public:
static constexpr uint64 alignment = get_alignment<Policy>::value;
using Policy::Policy;
T* allocate(size_type size, const_pointer hint = nullptr)
{
return Policy::allocate(size, hint);
}
void deallocate(pointer ptr, size_type size)
{
Policy::deallocate(ptr, size);
}
template<class... Args>
void construct(pointer ptr, Args... args)
{
new(ptr) value_type(std::forward<Args>(args)...);
}
void destroy(pointer ptr)
{
ptr->~value_type();
}
template<typename T1, typename T2>
friend bool operator==(const AllocationPolicy<T1>& lhs, const AllocationPolicy<T2>& rhs)
{
return lhs.equals(rhs);
}
template<typename T1, typename T2>
friend bool operator!=(const AllocationPolicy<T1>& lhs, const AllocationPolicy<T2>& rhs)
{
return !(lhs == rhs);
}
constexpr inline size_type max_size() const
{
return (std::numeric_limits<size_type>::max() / sizeof(T));
}
pointer address(reference ref) const
{
return std::addressof(ref);
}
const_pointer address(const_reference ref) const
{
return std::addressof(ref);
}
template<typename U>
struct rebind
{
using other = Allocator<U, AllocationPolicy, PolicyArgs...>;
};
};
/*
Default allocation policy. Only call the native memory allocation functions.
NOTE : using operator new[]() and operator delete[]() could be wiser here than using malloc and free.
*/
template<typename T>
class DefaultAllocationPolicy
{
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
public:
explicit DefaultAllocationPolicy() noexcept
{}
inline explicit DefaultAllocationPolicy(const DefaultAllocationPolicy&) noexcept
{}
template<typename U>
inline explicit DefaultAllocationPolicy(const DefaultAllocationPolicy<U>& other) noexcept
{
DefaultAllocationPolicy(static_cast<rebind_allocator_t<::DefaultAllocationPolicy, T>>(other));
}
~DefaultAllocationPolicy(){}
T* allocate(size_type size, const T* = nullptr) const noexcept
{
return static_cast<T*>(malloc(size * sizeof(T)));
}
void deallocate(T* ptr, size_type) const noexcept
{
free(ptr);
}
template<typename U>
bool equals(const DefaultAllocationPolicy<U>&) const noexcept
{
return true;
}
};
template<typename T>
using DefaultAllocator = Allocator<T, DefaultAllocationPolicy>;
#endif // ALLOCATOR
/*
Not a true .cxx, only to make gist colorize it.
*/
template<typename T,
class capacity,
class allocation,
class safety,
class MemoryAllocator>
ArenaAllocationPolicy<T, capacity, allocation, safety, MemoryAllocator>::ArenaAllocationPolicy()
noexcept(noexcept(newChunk(capacity::value)))
{
newChunk(capacity::value);
}
template<typename T,
class capacity,
class allocation,
class safety,
class MemoryAllocator>
ArenaAllocationPolicy<T, capacity, allocation, safety, MemoryAllocator>::ArenaAllocationPolicy(const ArenaAllocationPolicy& other)
noexcept(noexcept(newChunk(capacity::value)))
: ArenaAllocationPolicy()
{
std::memcpy(chunkHandle_, other.chunkHandle_, (capacity::value * sizeof(Node_)) + padding);
}
template<typename T,
class capacity,
class allocation,
class safety,
class MemoryAllocator>
ArenaAllocationPolicy<T, capacity, allocation, safety, MemoryAllocator>::~ArenaAllocationPolicy() noexcept
{
static_cast<BaseAllocator>(*this).deallocate(reinterpret_cast<T*>(chunkHandle_), capacity::value);
}
template<typename T,
class capacity,
class allocation,
class safety,
class MemoryAllocator>
T* ArenaAllocationPolicy<T, capacity, allocation, safety, MemoryAllocator>::allocate(size_type, const T* hint) noexcept
{
intptr_t returnedPtr = ((currentNode_->offset_));
bool isFull = (returnedPtr == 0);
Node_* tmp = (!isFull ? currentNode_ : nullptr);
currentNode_ += (returnedPtr + isFull);
return reinterpret_cast<T*>(tmp);
}
template<typename T,
class capacity,
class allocation,
class safety,
class MemoryAllocator>
template<Safety type,
std::enable_if_t<type == Safety::Enabled>*>
void ArenaAllocationPolicy<T, capacity, allocation, safety, MemoryAllocator>::deallocate(T* ptr, size_type size)
noexcept(noexcept(deallocate_(ptr, size)))
{
Node_* ptrNode = reinterpret_cast<Node_*>(ptr);
if(((reinterpret_cast<intptr_t>(ptrNode) & alignment) == 0)
&& ((ptrNode >= roundUpPtr(chunkHandle_, padding)) || (ptrNode < roundUpPtr(chunkHandle_ + capacity::value, padding))))
{
deallocate_(ptr, size);
}
}
template<typename T,
class capacity,
class allocation,
class safety,
class MemoryAllocator>
template<Safety type,
std::enable_if_t<type == Safety::Disabled>*>
void ArenaAllocationPolicy<T, capacity, allocation, safety, MemoryAllocator>::deallocate(T* ptr, size_type size)
noexcept(noexcept(deallocate_(ptr, size)))
{
deallocate_(ptr, size);
}
template<typename T,
class capacity,
class allocation,
class safety,
class MemoryAllocator>
template<AllocationType type,
std::enable_if_t<type == AllocationType::Aligned>*>
auto ArenaAllocationPolicy<T, capacity, allocation, safety, MemoryAllocator>::newChunk(size_type size, const T* hint)
noexcept(noexcept(std::declval<BaseAllocator>().allocate(size, hint))) -> Node_*
{
chunkHandle_ = reinterpret_cast<Node_*>(static_cast<BaseAllocator>(*this).allocate(size + alignmentSpace));
currentNode_ = roundUpPtr(chunkHandle_, padding);
std::fill_n(currentNode_, size, 1);
return currentNode_;
}
template<typename T,
class capacity,
class allocation,
class safety,
class MemoryAllocator>
template<AllocationType type,
std::enable_if_t<type == AllocationType::Unaligned>*>
auto ArenaAllocationPolicy<T, capacity, allocation, safety, MemoryAllocator>::newChunk(size_type size, const T* hint)
noexcept(noexcept(std::declval<BaseAllocator>().allocate(size, hint))) -> Node_*
{
chunkHandle_ = reinterpret_cast<Node_*>(static_cast<BaseAllocator>(*this).allocate(size));
std::fill_n(currentNode_, size, 1);
return currentNode_;
}
template<typename T,
class capacity,
class allocation,
class safety,
class MemoryAllocator>
void ArenaAllocationPolicy<T, capacity, allocation, safety, MemoryAllocator>::deallocate_(T* ptr, size_type size)
noexcept(noexcept(std::declval<BaseAllocator>().deallocate(ptr, size)))
{
Node_* ptrNode = reinterpret_cast<Node_*>(ptr);
static_cast<BaseAllocator>(*this).destroy(ptr);
ptrNode->offset_ = currentNode_ - ptrNode;
currentNode_ = ptrNode;
}
template<typename T,
class capacity,
class allocation,
class safety,
class MemoryAllocator>
template<AllocationType type,
std::enable_if_t<type == AllocationType::Aligned>*>
bool ArenaAllocationPolicy<T, capacity, allocation, safety, MemoryAllocator>::equals(const ArenaAllocationPolicy& other) noexcept
{
return (std::memcmp(roundUpPtr(chunkHandle_, padding), roundUpPtr(other.chunkHandle_, padding), capacity::value + alignmentSpace) == 0);
}
template<typename T,
class capacity,
class allocation,
class safety,
class MemoryAllocator>
template<AllocationType type,
std::enable_if_t<type == AllocationType::Unaligned>*>
bool ArenaAllocationPolicy<T, capacity, allocation, safety, MemoryAllocator>::equals(const ArenaAllocationPolicy& other) noexcept
{
return (std::memcmp(chunkHandle_, other.chunkHandle_, capacity::value) == 0);
}
#ifndef ARENA_ALLOCATOR_HXX
#define ARENA_ALLOCATOR_HXX
#include <Allocator.hxx>
#include <MetaMinimal.hxx>
//#include <Variant.hxx>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <type_traits>
#include <utility>
#include <cmath>
#include <cstdlib>
/*
These flags are used to change the allocation type of the arena allocator.
Aligned will guarantee that the memory will always be aligned, but consume a bit more space
(it allocate the space to perform the alignment), and can be less performant, due to memory
roundup, if the memory is already aligned.
Default : Aligned
*/
enum class AllocationType : uint8
{
Aligned,
Unaligned
};
/*
These flags are used to turn the safety on and off. The safety mod add some checks here and there,
and doing so incure an extremly tiny performance penalty.
Default : Safe
*/
enum class Safety : uint8
{
Enabled,
Disabled
};
/*
Performs memory address roundup to the closest "alignment" value, using the well known roundup algorithm
*/
template<class T>
T* roundUpPtr(T* ptr, uint32 alignment)
{
size_t mask = alignment != 0 ? alignment - 1 : 0;
return reinterpret_cast<T*>((reinterpret_cast<uintptr_t>(ptr) + mask) & ~mask);
}
/*
Arena allocator policy class. And arena allocator is basically only a fixed pool allocator, using pointer bumping
technic. The allocator is also optimized for object which sizeof(T) < sizeof(T*) (like 16bits types, i.e int16,
in 64bits computers).
Args :
- T = The type to allocate
- capacity = an integral constant, describing the capacity of the arena
- allocation = an integral constant, describing the allocation type (AllocationType)
- safety = an integral constant, describing the safety mod (Safety)
- MemoryAllocator = the base memory allocator, used to allocate/deallocate the main chunk, construct/destruct objects
*/
template<typename T,
class capacity = std::integral_constant<size_t, 4096>,
class allocation = std::integral_constant<AllocationType, AllocationType::Aligned>,
class safety = std::integral_constant<Safety, Safety::Enabled>,
class MemoryAllocator = DefaultAllocator<T>>
class ArenaAllocationPolicy : public MemoryAllocator::template rebind<T>::other
{
static_assert(capacity::value > 0,
"Arena allocator with capacity of 0 is invalid !");
protected:
union Node_;
using BaseAllocator = typename MemoryAllocator::template rebind<T>::other;
static constexpr uint64 alignment = std::conditional<allocation::value == AllocationType::Aligned,
std::integral_constant<uint64, alignof(T)>,
std::integral_constant<uint64, 0>>::type::value;
static constexpr uint64 padding = (alignment > BaseAllocator::alignment ? alignment : 0);
const uint64 alignmentSpace = std::ceil(static_cast<float>(alignment)/sizeof(T));
public:
typedef size_t size_type;
typedef ptrdiff difference_type;
typedef uint64* uint_ptr;
public:
ArenaAllocationPolicy() noexcept(noexcept(newChunk(capacity::value)));
explicit ArenaAllocationPolicy(const ArenaAllocationPolicy& other) noexcept(noexcept(newChunk(capacity::value)));
~ArenaAllocationPolicy() noexcept;
/*
Allocate an object, and return a pointer to it.
*/
T* allocate(size_type size, const T* hint = nullptr) noexcept;
/*
Safety mode enabled.
To provide a strong ownership guarantee, we use the fact that alignment is always a power of two, so
we can use some bit-wise operation to perform the modulus.
*/
template<Safety type = safety::value,
std::enable_if_t<type == Safety::Enabled>* = nullptr>
void deallocate(T* ptr, size_type size) noexcept(noexcept(deallocate_(ptr, size)));
/*
Safety mode enabled.
No check performed, and the object is destroyed immediatly.
*/
template<Safety type = safety::value,
std::enable_if_t<type == Safety::Disabled>* = nullptr>
void deallocate(T* ptr, size_type size) noexcept(noexcept(deallocate_(ptr, size)));
protected:
/*
Aligned allocations.
Allocates a new chunk of memory. The returned pointer is correctly aligned.
*/
template<AllocationType type = allocation::value,
std::enable_if_t<type == AllocationType::Aligned>* = nullptr>
Node_* newChunk(size_type size, const T* hint = nullptr) noexcept(noexcept(std::declval<BaseAllocator>().allocate(size, hint)));
/*
Unaligned allocations.
Allocates a new chunk of memory. The returned pointer is not guaranteed to be aligned.
*/
template<AllocationType type = allocation::value,
std::enable_if_t<type == AllocationType::Unaligned>* = nullptr>
Node_* newChunk(size_type size, const T* hint = nullptr) noexcept(noexcept(std::declval<BaseAllocator>().allocate(size, hint)));
void deallocate_(T* ptr, size_type size) noexcept(noexcept(std::declval<BaseAllocator>().deallocate(ptr, size)));
/*
Function used by the allocator creation helper to generate the operator== and operator!= definition
*/
template<AllocationType type = (padding == 0 ? AllocationType::Unaligned : allocation::value),
std::enable_if_t<type == AllocationType::Aligned>* = nullptr>
bool equals(const ArenaAllocationPolicy& other) noexcept;
template<AllocationType type = (padding == 0 ? AllocationType::Unaligned : allocation::value),
std::enable_if_t<type == AllocationType::Unaligned>* = nullptr>
bool equals(const ArenaAllocationPolicy& other) noexcept;
Node_* currentNode_;
Node_* chunkHandle_;
union Node_
{
T object_;
static constexpr size_t typeSize = ((sizeof(T) < sizeof(intptr_t) ? sizeof(T) : sizeof(intptr_t)));
sized_integer_t<(typeSize != 1 ? roundUp<typeSize, 1>::value : typeSize)> offset_;
Node_& operator=(const intptr_t rhs)
{
offset_ = rhs;
return *this;
}
};
};
/*
The actual allocator type, using the allocator creation helper
*/
template<typename T,
size_t capacity = 4096,
AllocationType allocation = AllocationType::Aligned,
Safety safety = Safety::Enabled,
class MemoryAllocator = DefaultAllocator<T>>
using ArenaAllocator = Allocator<T,
ArenaAllocationPolicy,
std::integral_constant<size_t, capacity>,
std::integral_constant<AllocationType, allocation>,
std::integral_constant<Safety, safety>,
MemoryAllocator>;
// Include the implementation
#include <Arena.cxx>
#endif // ARENA_ALLOCATOR_HXX
#ifndef COMMONTYPES
#define COMMONTYPES
#include <limits>
#include <cstddef>
#include <cstdint>
typedef uint64_t allocation_size_type;
typedef ptrdiff_t ptrdiff;
typedef int8_t int8;
typedef int16_t int16;
typedef int32_t int32;
typedef int64_t int64;
typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef uint64_t uint64;
typedef size_t max_type;
#endif // COMMONTYPES
#ifndef META_MINIMAL_HXX
#define META_MINIMAL_HXX
// A very very small subset of my already small template metaprogramming library.
#include <CommonTypes.hxx>
struct neutral {};
template<class ...>
using void_t = void;
template<class T>
struct identity
{
using type = T;
};
template<class MetaFunction>
using invoke = typename MetaFunction::type;
template<uint8 byteNumber>
struct sized_integer;
template<>
struct sized_integer<1> : identity<uint8> {};
template<>
struct sized_integer<2> : identity<uint16> {};
template<>
struct sized_integer<4> : identity<uint32> {};
template<>
struct sized_integer<8> : identity<uint64> {};
template<size_t byteNumber>
using sized_integer_t = invoke<sized_integer<byteNumber>>;
// This metafunction do power of two roundup
template<size_t num, size_t pow2>
struct roundUp
{
static constexpr size_t mask = ((1 << pow2) - 1);
static constexpr size_t value = ((num + mask) & ~mask);
};
template<size_t num>
struct roundUp<num, 0>
{
static constexpr size_t value = num;
};
template<size_t num>
struct roundUp<num, sizeof(size_t)*8>
{
static constexpr size_t mask = ~(std::numeric_limits<size_t>::max() >> 1);
static constexpr size_t value = (num & ~(std::numeric_limits<size_t>::max() >> 1));
};
#endif // META_MINIMAL_HXX
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment