25#ifndef JOIN_CORE_ALLOCATOR_HPP
26#define JOIN_CORE_ALLOCATOR_HPP
67 template <
size_t Size>
70 static_assert (
isPow2 (Size),
"size must be a power of 2");
71 static_assert (Size %
alignof (std::max_align_t) == 0,
"size must respects maximum alignment requirement");
72 static_assert (Size >=
sizeof (uint64_t),
"size must respects minimum size for storing index");
84 template <
size_t Size>
90 static constexpr uint64_t MAGIC = 0x9F7E3B2A8D5C4E1B;
93 static constexpr uint32_t NULL_IDX = UINT32_MAX;
96 alignas (64) std::atomic_uint64_t _magic;
99 alignas (64) std::atomic_uint64_t _head;
108 template <
size_t Count,
size_t Size>
111 static_assert (Count > 0,
"count must be at least 1");
112 static_assert (Count <= UINT32_MAX,
"count exceeds tagged index capacity (~256 GB)");
118 static constexpr size_t _count = Count;
121 static constexpr size_t _size = Size;
124 static constexpr size_t _stride =
sizeof (
Chunk);
127 static constexpr size_t _total =
sizeof (
Segment) + _stride * _count;
134 : _segment (
static_cast<Segment*
> (ptr))
136 uint64_t expected = 0;
138 if (_segment->_magic.compare_exchange_strong (expected, 0xFFFFFFFFFFFFFFFF, std::memory_order_acq_rel))
142 _segment->_magic.store (Segment::MAGIC, std::memory_order_release);
147 while (_segment->_magic.load (std::memory_order_acquire) != Segment::MAGIC)
171 : _segment (other._segment)
173 other._segment =
nullptr;
183 _segment = other._segment;
184 other._segment =
nullptr;
205 cur.
raw = _segment->_head.load (std::memory_order_acquire);
214 next.
idx = _segment->_chunks[cur.
idx]._next;
217 if (
JOIN_LIKELY (_segment->_head.compare_exchange_weak (cur.
raw, next.
raw, std::memory_order_acq_rel,
218 std::memory_order_acquire)))
220 return &_segment->_chunks[cur.
idx];
232 next.
idx =
static_cast<uint32_t
> (
reinterpret_cast<Chunk*
> (p) - _segment->_chunks);
233 cur.
raw = _segment->_head.load (std::memory_order_relaxed);
237 _segment->_chunks[next.
idx]._next = cur.
idx;
240 if (
JOIN_LIKELY (_segment->_head.compare_exchange_weak (cur.
raw, next.
raw, std::memory_order_release,
241 std::memory_order_relaxed)))
255 cur.
raw = _segment->_head.load (std::memory_order_acquire);
259 for (uint32_t idx = cur.
idx; idx != Segment::NULL_IDX; idx = _segment->_chunks[idx]._next)
269 next.
idx = Segment::NULL_IDX;
272 return _segment->_head.compare_exchange_strong (cur.
raw, next.
raw, std::memory_order_acq_rel,
273 std::memory_order_acquire);
281 for (uint32_t i = 0; i < _count - 1; ++i)
283 _segment->_chunks[i]._next = i + 1;
285 _segment->_chunks[_count - 1]._next = Segment::NULL_IDX;
288 cur.
raw = _segment->_head.load (std::memory_order_relaxed);
292 _segment->_head.store (next.
raw, std::memory_order_release);
300 bool owns (
void* p)
const noexcept
302 auto base =
reinterpret_cast<std::uintptr_t
> (_segment->_chunks);
303 auto end =
base + (_count * _stride);
304 auto ptr =
reinterpret_cast<std::uintptr_t
> (p);
305 return ((ptr >=
base) && (ptr < end));
315 return static_cast<uint32_t
> (
reinterpret_cast<Chunk*
> (p) - _segment->_chunks);
323 void*
getPtr (uint32_t idx)
const noexcept
325 return &_segment->_chunks[idx];
335 template <
size_t Count,
size_t... Sizes>
341 template <
size_t Count,
size_t First,
size_t... Rest>
350 template <
size_t Count,
size_t Last>
359 template <
size_t... Sizes>
365 template <
size_t First,
size_t Second,
size_t... Rest>
367 : std::integral_constant<bool, (First <= Second) && Sorted<Second, Rest...>::value>
374 template <size_t Last>
375 struct Sorted<Last> : std::true_type
383 struct Sorted<> : std::true_type
390 template <typename Backend, size_t Count, size_t... Sizes>
393 static_assert (sizeof...(Sizes) > 0, "arena must have at least one pool size");
394 static_assert (Sorted<Sizes...>::value, "pool sizes must be provided in ascending order");
398 static constexpr size_t _total =
TotalSize<Count, Sizes...>::value;
404 template <typename... Args>
406 : _backend (_total, std::forward<Args> (args)...)
407 , _pools (makePools (std::make_index_sequence<sizeof...(Sizes)>{}))
428 : _backend (std::move (other._backend))
429 , _pools (std::move (other._pools))
440 _backend = std::move (other._backend);
441 _pools = std::move (other._pools);
457 return allocateImplem<0> (size);
467 return tryAllocateImplem<0> (size);
480 deallocateImplem<0> (p);
489 return _backend.mapped ();
497 template <
size_t I = 0>
500 static_assert (I <
sizeof...(Sizes),
"pool index out of range");
501 return std::get<I> (_pools).getIndex (p);
509 template <
size_t I = 0>
510 void*
getPtr (uint32_t idx)
const noexcept
512 static_assert (I <
sizeof...(Sizes),
"pool index out of range");
513 return std::get<I> (_pools).getPtr (idx);
520 template <
size_t I = 0>
523 static_assert (I <
sizeof...(Sizes),
"pool index out of range");
524 return std::get<I> (_pools).reserveAll ();
530 template <
size_t I = 0>
533 static_assert (I <
sizeof...(Sizes),
"pool index out of range");
534 std::get<I> (_pools).releaseAll ();
543 int mbind (
int numa)
const noexcept
545 return _backend.mbind (numa);
555 return _backend.mlock ();
563 static std::array<size_t,
sizeof...(Sizes)> makeOffsets ()
566 std::array<size_t,
sizeof...(Sizes)> offsets{};
567 for (
size_t i = 1; i <
sizeof...(Sizes); ++i)
569 offsets[i] = offsets[i - 1] + totals[i - 1];
579 template <
size_t... Is>
580 std::tuple<BasicPool<Count, Sizes>...> makePools (std::index_sequence<Is...>)
noexcept
582 auto offsets = makeOffsets ();
583 return std::tuple<BasicPool<Count, Sizes>...>{
584 BasicPool<Count, Sizes> (
static_cast<char*
> (_backend.get ()) + offsets[Is])...};
591 typename std::enable_if<(I <
sizeof...(Sizes)),
void*>::type allocateImplem (
size_t size)
noexcept
593 auto& pool = std::get<I> (_pools);
594 if (size <= pool._size)
596 void* p = pool.pop ();
602 return allocateImplem<I + 1> (size);
609 typename std::enable_if<(I >=
sizeof...(Sizes)),
void*>::type allocateImplem (
size_t)
noexcept
618 typename std::enable_if<(I <
sizeof...(Sizes)),
void*>::type tryAllocateImplem (
size_t size)
noexcept
620 auto& pool = std::get<I> (_pools);
621 if (size <= pool._size)
625 return tryAllocateImplem<I + 1> (size);
632 typename std::enable_if<(I >=
sizeof...(Sizes)),
void*>::type tryAllocateImplem (
size_t)
noexcept
641 typename std::enable_if<(I <
sizeof...(Sizes))>::type deallocateImplem (
void* p)
noexcept
643 auto& pool = std::get<I> (_pools);
649 deallocateImplem<I + 1> (p);
656 typename std::enable_if<(I >=
sizeof...(Sizes))>::type deallocateImplem (
void*)
noexcept
664 std::tuple<BasicPool<Count, Sizes>...> _pools;
adaptive backoff strategy for busy-wait loops.
Definition backoff.hpp:43
memory arena owning backend and managing one or more pools.
Definition memory.hpp:53
void * getPtr(uint32_t idx) const noexcept
get the pointer to a chunk by index.
Definition allocator.hpp:510
bool hasBackend() const noexcept
check if the arena still owns a memory region.
Definition allocator.hpp:487
~BasicArena()=default
destroy instance.
BasicArena(const BasicArena &other)=delete
copy constructor.
int mlock() const noexcept
lock memory in RAM.
Definition allocator.hpp:553
uint32_t getIndex(void *p) const noexcept
get the index of a chunk in the pool.
Definition allocator.hpp:498
bool reserveAll() noexcept
reserve all chunks of a pool at once.
Definition allocator.hpp:521
void * allocate(size_t size) noexcept
allocate memory from the first pool that fits (promotes if exhausted).
Definition allocator.hpp:455
void * tryAllocate(size_t size) noexcept
allocate memory from the exact pool that fits, without promotion.
Definition allocator.hpp:465
BasicArena(BasicArena &&other) noexcept
move constructor.
Definition allocator.hpp:427
void releaseAll() noexcept
release all chunks of a pool at once.
Definition allocator.hpp:531
void deallocate(void *p) noexcept
return memory to the appropriate pool.
Definition allocator.hpp:474
Definition acceptor.hpp:32
constexpr bool isPow2(uint64_t value) noexcept
check if a value is a power of two.
Definition utils.hpp:496
std::string base(const std::string &filepath)
get base path of the specified file.
Definition filesystem.hpp:41
free-list pool operating over a pre-existing memory region.
Definition allocator.hpp:110
void push(void *p) noexcept
push a chunk back to the pool.
Definition allocator.hpp:229
uint32_t getIndex(void *p) const noexcept
get the index of a chunk in the pool.
Definition allocator.hpp:313
void * getPtr(uint32_t idx) const noexcept
get the pointer to a chunk by index.
Definition allocator.hpp:323
void * pop() noexcept
pop a chunk from the pool.
Definition allocator.hpp:197
BasicPool(const BasicPool &other)=delete
copy constructor.
BasicPool(BasicPool &&other) noexcept
move constructor.
Definition allocator.hpp:170
void releaseAll() noexcept
release all chunks at once.
Definition allocator.hpp:279
bool reserveAll() noexcept
reserve all chunks at once.
Definition allocator.hpp:252
bool owns(void *p) const noexcept
check if the pointer belongs to this pool.
Definition allocator.hpp:300
BasicPool(void *ptr) noexcept
initialize the pool over an existing memory region.
Definition allocator.hpp:133
~BasicPool()=default
destroy instance.
segment header.
Definition allocator.hpp:86
is sequence sorted.
Definition allocator.hpp:360
total size computation.
Definition allocator.hpp:336
basic chunk.
Definition allocator.hpp:69
uint32_t _next
index of next free chunk.
Definition allocator.hpp:75
tagged index.
Definition allocator.hpp:50
uint32_t gen
generation counter.
Definition allocator.hpp:57
uint32_t idx
free-list head index.
Definition allocator.hpp:54
uint64_t raw
raw value for atomic CAS.
Definition allocator.hpp:61
#define JOIN_LIKELY(x)
Definition utils.hpp:45
#define JOIN_UNLIKELY(x)
Definition utils.hpp:46