join 1.0
lightweight network framework library
Loading...
Searching...
No Matches
slab_allocator.hpp
Go to the documentation of this file.
1
25#ifndef JOIN_CORE_SLAB_ALLOCATOR_HPP
26#define JOIN_CORE_SLAB_ALLOCATOR_HPP
27
28// libjoin.
29#include <join/backoff.hpp>
30#include <join/memory.hpp>
31#include <join/utils.hpp>
32
33// C++.
34#include <algorithm>
35#include <utility>
36#include <atomic>
37#include <array>
38#include <tuple>
39
40// C.
41#include <cassert>
42#include <cstdint>
43#include <cstddef>
44
45namespace join
46{
50 union alignas (uint64_t) SlabTaggedIndex
51 {
52 struct
53 {
55 uint32_t idx;
56
58 uint32_t gen;
59 };
60
62 uint64_t raw;
63 };
64
68 template <size_t Size>
69 union alignas (64) BasicSlabChunk
70 {
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");
74
76 std::atomic_uint32_t _next;
77
79 uint8_t _data[Size];
80 };
81
85 template <size_t Size>
87 {
89
91 static constexpr uint64_t MAGIC = 0x9F7E3B2A8D5C4E1B;
92
94 static constexpr uint32_t NULL_IDX = UINT32_MAX;
95
97 alignas (64) std::atomic_uint64_t _magic;
98
100 alignas (64) std::atomic_uint64_t _head;
101
103 Chunk _chunks[];
104 };
105
109 template <size_t Count, size_t Size>
111 {
112 static_assert (Count > 0, "count must be at least 1");
113 static_assert (Count <= UINT32_MAX, "count exceeds tagged index capacity (~256 GB)");
114
117
119 static constexpr size_t _count = Count;
120
122 static constexpr size_t _size = Size;
123
125 static constexpr size_t _stride = sizeof (Chunk);
126
128 static constexpr size_t _total = sizeof (Segment) + _stride * _count;
129
134 explicit BasicSlabPool (void* ptr) noexcept
135 : _segment (static_cast<Segment*> (ptr))
136 {
137 uint64_t expected = 0;
138
139 if (_segment->_magic.compare_exchange_strong (expected, 0xFFFFFFFFFFFFFFFF, std::memory_order_acq_rel))
140 {
141 releaseAll ();
142
143 _segment->_magic.store (Segment::MAGIC, std::memory_order_release);
144 }
145 else
146 {
147 Backoff backoff;
148 while (_segment->_magic.load (std::memory_order_acquire) != Segment::MAGIC)
149 {
150 backoff (); // LCOV_EXCL_LINE
151 }
152 }
153 }
154
159 BasicSlabPool (const BasicSlabPool& other) = delete;
160
165 BasicSlabPool& operator= (const BasicSlabPool& other) = delete;
166
171 BasicSlabPool (BasicSlabPool&& other) noexcept
172 : _segment (other._segment)
173 {
174 other._segment = nullptr;
175 }
176
182 BasicSlabPool& operator= (BasicSlabPool&& other) noexcept
183 {
184 _segment = other._segment;
185 other._segment = nullptr;
186 return *this;
187 }
188
192 ~BasicSlabPool () = default;
193
198 void* pop () noexcept
199 {
200 if (JOIN_UNLIKELY (_segment == nullptr))
201 {
202 return nullptr;
203 }
204
205 SlabTaggedIndex cur, next;
206 cur.raw = _segment->_head.load (std::memory_order_acquire);
207
208 for (;;)
209 {
210 if (JOIN_UNLIKELY (cur.idx == Segment::NULL_IDX))
211 {
212 return nullptr;
213 }
214
215 next.idx = _segment->_chunks[cur.idx]._next.load (std::memory_order_relaxed);
216 next.gen = cur.gen + 1;
217
218 if (JOIN_LIKELY (_segment->_head.compare_exchange_weak (cur.raw, next.raw, std::memory_order_acq_rel,
219 std::memory_order_acquire)))
220 {
221 return &_segment->_chunks[cur.idx];
222 }
223 }
224 }
225
230 void push (void* p) noexcept
231 {
232 SlabTaggedIndex cur, next;
233 next.idx = static_cast<uint32_t> (reinterpret_cast<Chunk*> (p) - _segment->_chunks);
234 cur.raw = _segment->_head.load (std::memory_order_relaxed);
235
236 for (;;)
237 {
238 _segment->_chunks[next.idx]._next.store (cur.idx, std::memory_order_relaxed);
239 next.gen = cur.gen + 1;
240
241 if (JOIN_LIKELY (_segment->_head.compare_exchange_weak (cur.raw, next.raw, std::memory_order_release,
242 std::memory_order_relaxed)))
243 {
244 return;
245 }
246 }
247 }
248
253 bool reserveAll () noexcept
254 {
255 SlabTaggedIndex cur, next;
256 cur.raw = _segment->_head.load (std::memory_order_acquire);
257
258 uint32_t n = 0;
259
260 for (uint32_t idx = cur.idx; idx != Segment::NULL_IDX;
261 idx = _segment->_chunks[idx]._next.load (std::memory_order_relaxed))
262 {
263 ++n;
264 }
265
266 if (n != _count)
267 {
268 return false;
269 }
270
271 next.idx = Segment::NULL_IDX;
272 next.gen = cur.gen + 1;
273
274 return _segment->_head.compare_exchange_strong (cur.raw, next.raw, std::memory_order_acq_rel,
275 std::memory_order_acquire);
276 }
277
281 void releaseAll () noexcept
282 {
283 for (uint32_t i = 0; i < _count - 1; ++i)
284 {
285 _segment->_chunks[i]._next.store (i + 1, std::memory_order_relaxed);
286 }
287 _segment->_chunks[_count - 1]._next.store (Segment::NULL_IDX, std::memory_order_relaxed);
288
289 SlabTaggedIndex cur, next;
290 cur.raw = _segment->_head.load (std::memory_order_relaxed);
291 next.idx = 0;
292 next.gen = cur.gen + 1;
293
294 _segment->_head.store (next.raw, std::memory_order_release);
295 }
296
302 bool owns (void* p) const noexcept
303 {
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));
308 }
309
315 uint32_t getIndex (void* p) const noexcept
316 {
317 return static_cast<uint32_t> (reinterpret_cast<Chunk*> (p) - _segment->_chunks);
318 }
319
325 void* getPtr (uint32_t idx) const noexcept
326 {
327 return &_segment->_chunks[idx];
328 }
329
331 Segment* _segment = nullptr;
332 };
333
337 template <typename Backend, size_t Count, size_t... Sizes>
339 {
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");
342
343 public:
345 static constexpr size_t _total = (BasicSlabPool<Count, Sizes>::_total + ...);
346
351 template <typename... Args>
352 BasicSlabAllocator (Args&&... args)
353 : _backend (_total, std::forward<Args> (args)...)
354 , _pools (makePools (std::make_index_sequence<sizeof...(Sizes)>{}))
355 {
356 }
357
362 BasicSlabAllocator (const BasicSlabAllocator& other) = delete;
363
368 BasicSlabAllocator& operator= (const BasicSlabAllocator& other) = delete;
369
375 : _backend (std::move (other._backend))
376 , _pools (std::move (other._pools))
377 {
378 }
379
385 BasicSlabAllocator& operator= (BasicSlabAllocator&& other) noexcept
386 {
387 _backend = std::move (other._backend);
388 _pools = std::move (other._pools);
389 return *this;
390 }
391
396
402 void* allocate (size_t size) noexcept
403 {
404 return allocateImplem<0> (size);
405 }
406
412 void* tryAllocate (size_t size) noexcept
413 {
414 return tryAllocateImplem<0> (size);
415 }
416
421 void deallocate (void* p) noexcept
422 {
423 if (p == nullptr)
424 {
425 return;
426 }
427 deallocateImplem<0> (p);
428 }
429
435 void deallocate (void* p, [[maybe_unused]] size_t size) noexcept
436 {
437 deallocate (p);
438 }
439
444 bool hasBackend () const noexcept
445 {
446 return _backend.mapped ();
447 }
448
454 template <size_t I = 0>
455 uint32_t getIndex (void* p) const noexcept
456 {
457 static_assert (I < sizeof...(Sizes), "pool index out of range");
458 return std::get<I> (_pools).getIndex (p);
459 }
460
466 template <size_t I = 0>
467 void* getPtr (uint32_t idx) const noexcept
468 {
469 static_assert (I < sizeof...(Sizes), "pool index out of range");
470 return std::get<I> (_pools).getPtr (idx);
471 }
472
477 template <size_t I = 0>
478 bool reserveAll () noexcept
479 {
480 static_assert (I < sizeof...(Sizes), "pool index out of range");
481 return std::get<I> (_pools).reserveAll ();
482 }
483
487 template <size_t I = 0>
488 void releaseAll () noexcept
489 {
490 static_assert (I < sizeof...(Sizes), "pool index out of range");
491 std::get<I> (_pools).releaseAll ();
492 }
493
494#ifdef JOIN_HAS_NUMA
500 int mbind (int numa) const noexcept
501 {
502 return _backend.mbind (numa);
503 }
504#endif
505
510 int mlock () const noexcept
511 {
512 return _backend.mlock ();
513 }
514
515 private:
520 static std::array<size_t, sizeof...(Sizes)> makeOffsets ()
521 {
522 size_t totals[] = {BasicSlabPool<Count, Sizes>::_total...};
523 std::array<size_t, sizeof...(Sizes)> offsets{};
524 for (size_t i = 1; i < sizeof...(Sizes); ++i)
525 {
526 offsets[i] = offsets[i - 1] + totals[i - 1];
527 }
528 return offsets;
529 }
530
536 template <size_t... Is>
537 std::tuple<BasicSlabPool<Count, Sizes>...> makePools (std::index_sequence<Is...>) noexcept
538 {
539 auto offsets = makeOffsets ();
540 return std::tuple<BasicSlabPool<Count, Sizes>...>{
541 BasicSlabPool<Count, Sizes> (static_cast<char*> (_backend.get ()) + offsets[Is])...};
542 }
543
547 template <size_t I>
548 void* allocateImplem (size_t size) noexcept
549 {
550 if constexpr (I < sizeof...(Sizes))
551 {
552 auto& pool = std::get<I> (_pools);
553 if (size <= pool._size)
554 {
555 void* p = pool.pop ();
556 if (p != nullptr)
557 {
558 return p;
559 }
560 }
561 return allocateImplem<I + 1> (size);
562 }
563 else
564 {
565 return nullptr;
566 }
567 }
568
572 template <size_t I>
573 void* tryAllocateImplem (size_t size) noexcept
574 {
575 if constexpr (I < sizeof...(Sizes))
576 {
577 auto& pool = std::get<I> (_pools);
578 if (size <= pool._size)
579 {
580 return pool.pop ();
581 }
582 return tryAllocateImplem<I + 1> (size);
583 }
584 else
585 {
586 return nullptr;
587 }
588 }
589
593 template <size_t I>
594 void deallocateImplem (void* p) noexcept
595 {
596 if constexpr (I < sizeof...(Sizes))
597 {
598 auto& pool = std::get<I> (_pools);
599 if (pool.owns (p))
600 {
601 pool.push (p);
602 return;
603 }
604 deallocateImplem<I + 1> (p);
605 }
606 }
607
609 Backend _backend;
610
612 std::tuple<BasicSlabPool<Count, Sizes>...> _pools;
613 };
614}
615
616#endif
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
Definition error.hpp:144
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