Last active
January 3, 2016 15:50
-
-
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.
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
| #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 |
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
| /* | |
| 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); | |
| } |
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
| #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 |
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
| #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 |
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
| #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