25#ifndef JOIN_CORE_SLAB_ALLOCATOR_HPP
26#define JOIN_CORE_SLAB_ALLOCATOR_HPP
68 template <
size_t Size>
71 static_assert (
isPow2 (Size),
"size must be a power of 2");
72 static_assert (Size %
alignof (std::max_align_t) == 0,
"size must respects maximum alignment requirement");
73 static_assert (Size >=
sizeof (uint64_t),
"size must respects minimum size for storing index");
85 template <
size_t Size>
91 static constexpr uint64_t MAGIC = 0x9F7E3B2A8D5C4E1B;
94 static constexpr uint32_t NULL_IDX = UINT32_MAX;
97 alignas (64) std::atomic_uint64_t _magic;
100 alignas (64) std::atomic_uint64_t _head;
109 template <
size_t Count,
size_t Size>
112 static_assert (Count > 0,
"count must be at least 1");
113 static_assert (Count <= UINT32_MAX,
"count exceeds tagged index capacity (~256 GB)");
119 static constexpr size_t _count = Count;
122 static constexpr size_t _size = Size;
125 static constexpr size_t _stride =
sizeof (
Chunk);
128 static constexpr size_t _total =
sizeof (
Segment) + _stride * _count;
135 : _segment (
static_cast<Segment*
> (ptr))
137 uint64_t expected = 0;
139 if (_segment->_magic.compare_exchange_strong (expected, 0xFFFFFFFFFFFFFFFF, std::memory_order_acq_rel))
143 _segment->_magic.store (Segment::MAGIC, std::memory_order_release);
148 while (_segment->_magic.load (std::memory_order_acquire) != Segment::MAGIC)
172 : _segment (other._segment)
174 other._segment =
nullptr;
184 _segment = other._segment;
185 other._segment =
nullptr;
206 cur.
raw = _segment->_head.load (std::memory_order_acquire);
215 next.
idx = _segment->_chunks[cur.
idx]._next.load (std::memory_order_relaxed);
218 if (
JOIN_LIKELY (_segment->_head.compare_exchange_weak (cur.
raw, next.
raw, std::memory_order_acq_rel,
219 std::memory_order_acquire)))
221 return &_segment->_chunks[cur.
idx];
233 next.
idx =
static_cast<uint32_t
> (
reinterpret_cast<Chunk*
> (p) - _segment->_chunks);
234 cur.
raw = _segment->_head.load (std::memory_order_relaxed);
238 _segment->_chunks[next.
idx]._next.store (cur.
idx, std::memory_order_relaxed);
241 if (
JOIN_LIKELY (_segment->_head.compare_exchange_weak (cur.
raw, next.
raw, std::memory_order_release,
242 std::memory_order_relaxed)))
256 cur.
raw = _segment->_head.load (std::memory_order_acquire);
260 for (uint32_t idx = cur.
idx; idx != Segment::NULL_IDX;
261 idx = _segment->_chunks[idx]._next.load (std::memory_order_relaxed))
271 next.
idx = Segment::NULL_IDX;
274 return _segment->_head.compare_exchange_strong (cur.
raw, next.
raw, std::memory_order_acq_rel,
275 std::memory_order_acquire);
283 for (uint32_t i = 0; i < _count - 1; ++i)
285 _segment->_chunks[i]._next.store (i + 1, std::memory_order_relaxed);
287 _segment->_chunks[_count - 1]._next.store (Segment::NULL_IDX, std::memory_order_relaxed);
290 cur.
raw = _segment->_head.load (std::memory_order_relaxed);
294 _segment->_head.store (next.
raw, std::memory_order_release);
302 bool owns (
void* p)
const noexcept
304 auto base =
reinterpret_cast<std::uintptr_t
> (_segment->_chunks);
305 auto end =
base + (_count * _stride);
306 auto ptr =
reinterpret_cast<std::uintptr_t
> (p);
307 return ((ptr >=
base) && (ptr < end));
317 return static_cast<uint32_t
> (
reinterpret_cast<Chunk*
> (p) - _segment->_chunks);
325 void*
getPtr (uint32_t idx)
const noexcept
327 return &_segment->_chunks[idx];
337 template <
typename Backend,
size_t Count,
size_t... Sizes>
340 static_assert (
sizeof...(Sizes) > 0,
"slab allocator must have at least one pool size");
341 static_assert (std::ranges::is_sorted (std::array{Sizes...}),
"pool sizes must be provided in ascending order");
351 template <
typename... Args>
353 : _backend (_total,
std::forward<Args> (args)...)
354 , _pools (makePools (
std::make_index_sequence<sizeof...(Sizes)>{}))
375 : _backend (std::move (other._backend))
376 , _pools (std::move (other._pools))
387 _backend = std::move (other._backend);
388 _pools = std::move (other._pools);
404 return allocateImplem<0> (size);
414 return tryAllocateImplem<0> (size);
427 deallocateImplem<0> (p);
435 void deallocate (
void* p, [[maybe_unused]]
size_t size)
noexcept
446 return _backend.mapped ();
454 template <
size_t I = 0>
457 static_assert (I <
sizeof...(Sizes),
"pool index out of range");
458 return std::get<I> (_pools).getIndex (p);
466 template <
size_t I = 0>
467 void*
getPtr (uint32_t idx)
const noexcept
469 static_assert (I <
sizeof...(Sizes),
"pool index out of range");
470 return std::get<I> (_pools).getPtr (idx);
477 template <
size_t I = 0>
480 static_assert (I <
sizeof...(Sizes),
"pool index out of range");
481 return std::get<I> (_pools).reserveAll ();
487 template <
size_t I = 0>
490 static_assert (I <
sizeof...(Sizes),
"pool index out of range");
491 std::get<I> (_pools).releaseAll ();
500 int mbind (
int numa)
const noexcept
502 return _backend.mbind (numa);
512 return _backend.mlock ();
520 static std::array<size_t,
sizeof...(Sizes)> makeOffsets ()
523 std::array<size_t,
sizeof...(Sizes)> offsets{};
524 for (
size_t i = 1; i <
sizeof...(Sizes); ++i)
526 offsets[i] = offsets[i - 1] + totals[i - 1];
536 template <
size_t... Is>
537 std::tuple<BasicSlabPool<Count, Sizes>...> makePools (std::index_sequence<Is...>)
noexcept
539 auto offsets = makeOffsets ();
540 return std::tuple<BasicSlabPool<Count, Sizes>...>{
541 BasicSlabPool<Count, Sizes> (
static_cast<char*
> (_backend.get ()) + offsets[Is])...};
548 void* allocateImplem (
size_t size)
noexcept
550 if constexpr (I <
sizeof...(Sizes))
552 auto& pool = std::get<I> (_pools);
553 if (size <= pool._size)
555 void* p = pool.pop ();
561 return allocateImplem<I + 1> (size);
573 void* tryAllocateImplem (
size_t size)
noexcept
575 if constexpr (I <
sizeof...(Sizes))
577 auto& pool = std::get<I> (_pools);
578 if (size <= pool._size)
582 return tryAllocateImplem<I + 1> (size);
594 void deallocateImplem (
void* p)
noexcept
596 if constexpr (I <
sizeof...(Sizes))
598 auto& pool = std::get<I> (_pools);
604 deallocateImplem<I + 1> (p);
612 std::tuple<BasicSlabPool<Count, Sizes>...> _pools;
adaptive backoff for busy-wait loops, requires Rdtsc::calibrate ().
Definition backoff.hpp:47
slab allocator owning a backend and managing one or more pools.
Definition slab_allocator.hpp:339
uint32_t getIndex(void *p) const noexcept
get the index of a chunk in the pool.
Definition slab_allocator.hpp:455
bool hasBackend() const noexcept
check if the slab allocator still owns a memory region.
Definition slab_allocator.hpp:444
BasicSlabAllocator(const BasicSlabAllocator &other)=delete
copy constructor.
void * allocate(size_t size) noexcept
allocate memory from the first pool that fits (promotes if exhausted).
Definition slab_allocator.hpp:402
~BasicSlabAllocator()=default
destroy instance.
BasicSlabAllocator(BasicSlabAllocator &&other) noexcept
move constructor.
Definition slab_allocator.hpp:374
BasicSlabAllocator(Args &&... args)
create instance.
Definition slab_allocator.hpp:352
void * tryAllocate(size_t size) noexcept
allocate memory from the exact pool that fits, without promotion.
Definition slab_allocator.hpp:412
void releaseAll() noexcept
release all chunks of a pool at once.
Definition slab_allocator.hpp:488
bool reserveAll() noexcept
reserve all chunks of a pool at once.
Definition slab_allocator.hpp:478
int mlock() const noexcept
lock memory in RAM.
Definition slab_allocator.hpp:510
void deallocate(void *p, size_t size) noexcept
return memory to the appropriate pool.
Definition slab_allocator.hpp:435
void * getPtr(uint32_t idx) const noexcept
get the pointer to a chunk by index.
Definition slab_allocator.hpp:467
void deallocate(void *p) noexcept
return memory to the appropriate pool.
Definition slab_allocator.hpp:421
Definition acceptor.hpp:32
constexpr bool isPow2(uint64_t value) noexcept
check if a value is a power of two.
Definition utils.hpp:465
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 slab_allocator.hpp:111
void * getPtr(uint32_t idx) const noexcept
get the pointer to a chunk by index.
Definition slab_allocator.hpp:325
void releaseAll() noexcept
release all chunks at once.
Definition slab_allocator.hpp:281
void * pop() noexcept
pop a chunk from the pool.
Definition slab_allocator.hpp:198
BasicSlabPool(const BasicSlabPool &other)=delete
copy constructor.
uint32_t getIndex(void *p) const noexcept
get the index of a chunk in the pool.
Definition slab_allocator.hpp:315
~BasicSlabPool()=default
destroy instance.
BasicSlabPool(BasicSlabPool &&other) noexcept
move constructor.
Definition slab_allocator.hpp:171
BasicSlabPool(void *ptr) noexcept
initialize the pool over an existing memory region.
Definition slab_allocator.hpp:134
void push(void *p) noexcept
push a chunk back to the pool.
Definition slab_allocator.hpp:230
bool owns(void *p) const noexcept
check if the pointer belongs to this pool.
Definition slab_allocator.hpp:302
bool reserveAll() noexcept
reserve all chunks at once.
Definition slab_allocator.hpp:253
segment header.
Definition slab_allocator.hpp:87
basic chunk.
Definition slab_allocator.hpp:70
std::atomic_uint32_t _next
index of next free chunk.
Definition slab_allocator.hpp:76
tagged index.
Definition slab_allocator.hpp:51
uint32_t gen
generation counter.
Definition slab_allocator.hpp:58
uint32_t idx
free-list head index.
Definition slab_allocator.hpp:55
uint64_t raw
raw value for atomic CAS.
Definition slab_allocator.hpp:62
#define JOIN_LIKELY(x)
Definition utils.hpp:50
#define JOIN_UNLIKELY(x)
Definition utils.hpp:51