Created
June 13, 2020 02:06
-
-
Save etorth/e14e955d0183ef606dffb3a2d42030c9 to your computer and use it in GitHub Desktop.
code review for allocator implementation
This file contains 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
#pragma once | |
#include <cstdio> | |
#include <cstdlib> | |
#include <cstddef> | |
#include <cassert> | |
#include <vector> | |
#include <stdexcept> | |
#include "fflerror.h" // macro, same as std::runtime_error, but with file/function/line information | |
// #define SCOPED_ALLOC_USE_THROW | |
namespace scoped_alloc | |
{ | |
constexpr bool check_alignment(size_t alignment) | |
{ | |
return alignment && (alignment & (alignment - 1)) == 0; | |
} | |
template<size_t Alignment> struct aligned_buf | |
{ | |
constexpr static size_t alignment = Alignment; | |
static_assert(check_alignment(Alignment), "bad alignment"); | |
const char *buf = nullptr; | |
const size_t size = 0; | |
}; | |
template<size_t Alignment = alignof(std::max_align_t)> class arena_interf | |
{ | |
private: | |
static_assert(check_alignment(Alignment), "bad alignment"); | |
protected: | |
char *m_cursor = nullptr; | |
public: | |
virtual aligned_buf<Alignment> get_buf() const = 0; | |
public: | |
arena_interf() = default; | |
public: | |
arena_interf(arena_interf &&) = delete; | |
arena_interf(const arena_interf&) = delete; | |
arena_interf &operator = (const arena_interf &) = delete; | |
public: | |
virtual ~arena_interf() | |
{ | |
m_cursor = nullptr; | |
} | |
protected: | |
bool in_buf(char *p) const | |
{ | |
const auto buf = get_buf(); | |
if(!(buf.buf && buf.size)){ | |
throw fflerror("no valid buffer attached to arena_interf"); | |
} | |
return buf.buf <= p && p <= buf.buf + buf.size; | |
} | |
protected: | |
static size_t aligned_size(size_t byte_count) noexcept | |
{ | |
return (byte_count + (Alignment - 1)) & ~(Alignment - 1); | |
} | |
public: | |
template<size_t RequestAlignment> char *allocate(size_t byte_count) | |
{ | |
static_assert(RequestAlignment <= Alignment, "alignment too small"); | |
detect_outlive(); | |
const auto buf = get_buf(); | |
const auto byte_count_aligned = aligned_size(byte_count); | |
if(static_cast<decltype(byte_count_aligned)>(buf.buf + buf.size - m_cursor) >= byte_count_aligned){ | |
auto r = m_cursor; | |
m_cursor += byte_count_aligned; | |
return r; | |
} | |
// TODO normal operator new() only guarantees alignemnt <= alignof(std::max_align_t) | |
// but class user may ask for over-aligned memory, how to handle this? | |
// | |
// 1. raise compilation error if asks for over-alignment | |
// 2. here I try posix_memalign(), this may waste memory, or other issue? | |
// | |
return dynamic_alloc(byte_count); | |
} | |
public: | |
void deallocate(char *p, size_t byte_count) noexcept | |
{ | |
detect_outlive(); | |
if(in_buf(p)){ | |
if(p + aligned_size(byte_count) == m_cursor){ | |
m_cursor = p; | |
} | |
else{ | |
// TODO: main draw-back | |
// can't recycle memory here without extra flags | |
} | |
} | |
else{ | |
free(p); // allocated by posix_memalign() | |
} | |
} | |
public: | |
virtual char *dynamic_alloc(size_t byte_count) | |
{ | |
return aligned_alloc(byte_count); | |
} | |
public: | |
static char *aligned_alloc(size_t byte_count) | |
{ | |
void *aligned_ptr = nullptr; | |
if(!posix_memalign(&aligned_ptr, Alignment, byte_count)){ | |
return static_cast<char *>(aligned_ptr); | |
} | |
throw fflerror("posix_memalign(..., alignment = %zu, byte_count = %zu) failed", Alignment, byte_count); | |
} | |
private: | |
void detect_outlive() const | |
{ | |
// TODO: best-effort | |
// detect_outlive() invokes undefined behavior if assertion failed | |
#ifdef SCOPED_ALLOC_USE_THROW | |
if(!(in_buf(m_cursor))){ | |
throw fflerror("allocator has outlived arena_interf"); | |
} | |
#else | |
assert(in_buf(m_cursor) && "allocator has outlived arena_interf"); | |
#endif | |
} | |
}; | |
template<size_t ByteCount, size_t Alignment = alignof(std::max_align_t)> class fixed_arena: public scoped_alloc::arena_interf<Alignment> | |
{ | |
private: | |
alignas(Alignment) char m_buf[ByteCount]; | |
public: | |
fixed_arena(): scoped_alloc::arena_interf<Alignment>() | |
{ | |
this->m_cursor = m_buf; | |
} | |
public: | |
aligned_buf<Alignment> get_buf() const override | |
{ | |
return {m_buf, ByteCount}; | |
} | |
}; | |
template<size_t Alignment = alignof(std::max_align_t)> class dynamic_arena: public scoped_alloc::arena_interf<Alignment> | |
{ | |
private: | |
char *m_buf = nullptr; | |
size_t m_len = 0; | |
public: | |
dynamic_arena(size_t byte_count = 0): scoped_alloc::arena_interf<Alignment>() | |
{ | |
if(!byte_count){ | |
return; | |
} | |
alloc(byte_count); | |
} | |
dynamic_arena(const scoped_alloc::aligned_buf<Alignment> &buf) | |
: scoped_alloc::arena_interf<Alignment>() | |
, m_buf(buf.buf) | |
, m_len(buf.size) | |
{ | |
if(!(m_buf && m_len)){ | |
throw fflerror("invalid aligned_buf<%zu>(buf = %p, size = %zu)", Alignment, m_buf, m_len); | |
} | |
this->m_cursor = m_buf; | |
} | |
public: | |
~dynamic_arena() override | |
{ | |
if(m_buf){ | |
free(m_buf); | |
} | |
m_buf = nullptr; | |
m_len = 0; | |
} | |
public: | |
aligned_buf<Alignment> get_buf() const override | |
{ | |
return {m_buf, m_len}; | |
} | |
public: | |
void alloc(size_t byte_count) | |
{ | |
if(auto new_buf = scoped_alloc::arena_interf<Alignment>::aligned_alloc(byte_count)){ | |
if(m_buf){ | |
free(m_buf); | |
} | |
m_buf = new_buf; | |
m_len = byte_count; | |
this->m_cursor = new_buf; | |
} | |
} | |
public: | |
aligned_buf<Alignment> steal_buf() | |
{ | |
auto buf = m_buf; | |
auto len = m_len; | |
m_buf = nullptr; | |
m_len = 0; | |
this->m_cursor = nullptr; | |
return {buf, len}; | |
} | |
}; | |
template<class T, size_t Alignment = alignof(std::max_align_t)> class allocator | |
{ | |
public: | |
using value_type = T; | |
template <class U, size_t A> friend class scoped_alloc::allocator; | |
public: | |
template <class UpperType> struct rebind | |
{ | |
using other = allocator<UpperType, Alignment>; | |
}; | |
private: | |
arena_interf<Alignment> &m_arena; | |
public: | |
allocator(const allocator &) = default; | |
allocator &operator=(const allocator &) = delete; | |
public: | |
allocator(arena_interf<Alignment> &arena_ref) noexcept | |
: m_arena(arena_ref) | |
{} | |
public: | |
template<class U> allocator(const allocator<U, Alignment>& alloc_ref) noexcept | |
: m_arena(alloc_ref.m_arena) | |
{} | |
public: | |
T* allocate(size_t n) | |
{ | |
return reinterpret_cast<T *>(m_arena.template allocate<alignof(T)>(n * sizeof(T))); | |
} | |
void deallocate(T *p, size_t n) noexcept | |
{ | |
m_arena.deallocate(reinterpret_cast<char *>(p), n * sizeof(T)); | |
} | |
public: | |
template<typename T2, size_t A2> bool operator == (const scoped_alloc::allocator<T2, A2> &parm) noexcept | |
{ | |
return Alignment == A2 && &(this->m_arena) == &(parm.m_arena); | |
} | |
template<typename T2, size_t A2> bool operator != (const scoped_alloc::allocator<T2, A2> &parm) noexcept | |
{ | |
return !(*this == parm); | |
} | |
}; | |
} | |
template<typename T, size_t N> class svobuf | |
{ | |
private: | |
scoped_alloc::fixed_arena<N * sizeof(T), alignof(T)> m_arena; | |
public: | |
std::vector<T, scoped_alloc::allocator<T, alignof(T)>> c; | |
public: | |
svobuf() | |
: c(typename decltype(c)::allocator_type(m_arena)) | |
{ | |
// immediately use all static buffer | |
// 1. to prevent push_back() to allocate on heap, best effort | |
// 2. to prevent waste of memory | |
// | |
// svobuf<int, 4> buf; | |
// auto buf_cp = buf.c; | |
// | |
// buf_cp.push_back(1); | |
// buf_cp.push_back(2); | |
// | |
// here buf has ran out of all static buffer | |
// the copy constructed buf_cp will always allocates memory on heap | |
// | |
// ill-formed code: | |
// | |
// auto f() | |
// { | |
// svobuf<int, 4> buf; | |
// | |
// buf.c.push_back(0); | |
// buf.c.push_back(1); | |
// ... | |
// | |
// return buf.c; | |
// } | |
// | |
// return a copy of buf.c also copies its scoped allocator | |
// this causes dangling reference | |
c.reserve(N); | |
if(c.capacity() > N){ | |
throw fflerror("allocate initial buffer dynamically"); | |
} | |
} | |
public: | |
constexpr size_t fixed_capacity() const | |
{ | |
return N; | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
test program: