join 1.0
lightweight network framework library
Loading...
Searching...
No Matches
allocator.hpp
Go to the documentation of this file.
1
25#ifndef JOIN_CORE_ALLOCATOR_HPP
26#define JOIN_CORE_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 <utility>
35#include <atomic>
36#include <array>
37#include <tuple>
38
39// C.
40#include <cassert>
41#include <cstdint>
42#include <cstddef>
43
44namespace join
45{
49 union alignas (uint64_t) TaggedIndex
50 {
51 struct
52 {
54 uint32_t idx;
55
57 uint32_t gen;
58 };
59
61 uint64_t raw;
62 };
63
67 template <size_t Size>
68 union alignas (64) BasicChunk
69 {
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");
73
75 uint32_t _next;
76
78 uint8_t _data[Size];
79 };
80
84 template <size_t Size>
86 {
88
90 static constexpr uint64_t MAGIC = 0x9F7E3B2A8D5C4E1B;
91
93 static constexpr uint32_t NULL_IDX = UINT32_MAX;
94
96 alignas (64) std::atomic_uint64_t _magic;
97
99 alignas (64) std::atomic_uint64_t _head;
100
102 Chunk _chunks[];
103 };
104
108 template <size_t Count, size_t Size>
110 {
111 static_assert (Count > 0, "count must be at least 1");
112 static_assert (Count <= UINT32_MAX, "count exceeds tagged index capacity (~256 GB)");
113
116
118 static constexpr size_t _count = Count;
119
121 static constexpr size_t _size = Size;
122
124 static constexpr size_t _stride = sizeof (Chunk);
125
127 static constexpr size_t _total = sizeof (Segment) + _stride * _count;
128
133 explicit BasicPool (void* ptr) noexcept
134 : _segment (static_cast<Segment*> (ptr))
135 {
136 uint64_t expected = 0;
137
138 if (_segment->_magic.compare_exchange_strong (expected, 0xFFFFFFFFFFFFFFFF, std::memory_order_acq_rel))
139 {
140 releaseAll ();
141
142 _segment->_magic.store (Segment::MAGIC, std::memory_order_release);
143 }
144 else
145 {
146 Backoff backoff;
147 while (_segment->_magic.load (std::memory_order_acquire) != Segment::MAGIC)
148 {
149 backoff (); // LCOV_EXCL_LINE
150 }
151 }
152 }
153
158 BasicPool (const BasicPool& other) = delete;
159
164 BasicPool& operator= (const BasicPool& other) = delete;
165
170 BasicPool (BasicPool&& other) noexcept
171 : _segment (other._segment)
172 {
173 other._segment = nullptr;
174 }
175
181 BasicPool& operator= (BasicPool&& other) noexcept
182 {
183 _segment = other._segment;
184 other._segment = nullptr;
185 return *this;
186 }
187
191 ~BasicPool () = default;
192
197 void* pop () noexcept
198 {
199 if (JOIN_UNLIKELY (_segment == nullptr))
200 {
201 return nullptr;
202 }
203
204 TaggedIndex cur, next;
205 cur.raw = _segment->_head.load (std::memory_order_acquire);
206
207 for (;;)
208 {
209 if (JOIN_UNLIKELY (cur.idx == Segment::NULL_IDX))
210 {
211 return nullptr;
212 }
213
214 next.idx = _segment->_chunks[cur.idx]._next;
215 next.gen = cur.gen + 1;
216
217 if (JOIN_LIKELY (_segment->_head.compare_exchange_weak (cur.raw, next.raw, std::memory_order_acq_rel,
218 std::memory_order_acquire)))
219 {
220 return &_segment->_chunks[cur.idx];
221 }
222 }
223 }
224
229 void push (void* p) noexcept
230 {
231 TaggedIndex cur, next;
232 next.idx = static_cast<uint32_t> (reinterpret_cast<Chunk*> (p) - _segment->_chunks);
233 cur.raw = _segment->_head.load (std::memory_order_relaxed);
234
235 for (;;)
236 {
237 _segment->_chunks[next.idx]._next = cur.idx;
238 next.gen = cur.gen + 1;
239
240 if (JOIN_LIKELY (_segment->_head.compare_exchange_weak (cur.raw, next.raw, std::memory_order_release,
241 std::memory_order_relaxed)))
242 {
243 return;
244 }
245 }
246 }
247
252 bool reserveAll () noexcept
253 {
254 TaggedIndex cur, next;
255 cur.raw = _segment->_head.load (std::memory_order_acquire);
256
257 uint32_t n = 0;
258
259 for (uint32_t idx = cur.idx; idx != Segment::NULL_IDX; idx = _segment->_chunks[idx]._next)
260 {
261 ++n;
262 }
263
264 if (n != _count)
265 {
266 return false;
267 }
268
269 next.idx = Segment::NULL_IDX;
270 next.gen = cur.gen + 1;
271
272 return _segment->_head.compare_exchange_strong (cur.raw, next.raw, std::memory_order_acq_rel,
273 std::memory_order_acquire);
274 }
275
279 void releaseAll () noexcept
280 {
281 for (uint32_t i = 0; i < _count - 1; ++i)
282 {
283 _segment->_chunks[i]._next = i + 1;
284 }
285 _segment->_chunks[_count - 1]._next = Segment::NULL_IDX;
286
287 TaggedIndex cur, next;
288 cur.raw = _segment->_head.load (std::memory_order_relaxed);
289 next.idx = 0;
290 next.gen = cur.gen + 1;
291
292 _segment->_head.store (next.raw, std::memory_order_release);
293 }
294
300 bool owns (void* p) const noexcept
301 {
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));
306 }
307
313 uint32_t getIndex (void* p) const noexcept
314 {
315 return static_cast<uint32_t> (reinterpret_cast<Chunk*> (p) - _segment->_chunks);
316 }
317
323 void* getPtr (uint32_t idx) const noexcept
324 {
325 return &_segment->_chunks[idx];
326 }
327
329 Segment* _segment = nullptr;
330 };
331
335 template <size_t Count, size_t... Sizes>
336 struct TotalSize;
337
341 template <size_t Count, size_t First, size_t... Rest>
342 struct TotalSize<Count, First, Rest...>
343 {
344 static constexpr size_t value = BasicPool<Count, First>::_total + TotalSize<Count, Rest...>::value;
345 };
346
350 template <size_t Count, size_t Last>
351 struct TotalSize<Count, Last>
352 {
353 static constexpr size_t value = BasicPool<Count, Last>::_total;
354 };
355
359 template <size_t... Sizes>
360 struct Sorted;
361
365 template <size_t First, size_t Second, size_t... Rest>
366 struct Sorted<First, Second, Rest...>
367 : std::integral_constant<bool, (First <= Second) && Sorted<Second, Rest...>::value>
368 {
369 };
370
374 template <size_t Last>
375 struct Sorted<Last> : std::true_type
376 {
377 };
378
382 template <>
383 struct Sorted<> : std::true_type
384 {
385 };
386
390 template <typename Backend, size_t Count, size_t... Sizes>
391 class BasicArena
392 {
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");
395
396 public:
398 static constexpr size_t _total = TotalSize<Count, Sizes...>::value;
399
404 template <typename... Args>
405 BasicArena (Args&&... args)
406 : _backend (_total, std::forward<Args> (args)...)
407 , _pools (makePools (std::make_index_sequence<sizeof...(Sizes)>{}))
408 {
409 }
410
415 BasicArena (const BasicArena& other) = delete;
416
421 BasicArena& operator= (const BasicArena& other) = delete;
422
427 BasicArena (BasicArena&& other) noexcept
428 : _backend (std::move (other._backend))
429 , _pools (std::move (other._pools))
430 {
431 }
432
438 BasicArena& operator= (BasicArena&& other) noexcept
439 {
440 _backend = std::move (other._backend);
441 _pools = std::move (other._pools);
442 return *this;
443 }
444
448 ~BasicArena () = default;
449
455 void* allocate (size_t size) noexcept
456 {
457 return allocateImplem<0> (size);
458 }
459
465 void* tryAllocate (size_t size) noexcept
466 {
467 return tryAllocateImplem<0> (size);
468 }
469
474 void deallocate (void* p) noexcept
475 {
476 if (p == nullptr)
477 {
478 return;
479 }
480 deallocateImplem<0> (p);
481 }
482
487 bool hasBackend () const noexcept
488 {
489 return _backend.mapped ();
490 }
491
497 template <size_t I = 0>
498 uint32_t getIndex (void* p) const noexcept
499 {
500 static_assert (I < sizeof...(Sizes), "pool index out of range");
501 return std::get<I> (_pools).getIndex (p);
502 }
503
509 template <size_t I = 0>
510 void* getPtr (uint32_t idx) const noexcept
511 {
512 static_assert (I < sizeof...(Sizes), "pool index out of range");
513 return std::get<I> (_pools).getPtr (idx);
514 }
515
520 template <size_t I = 0>
521 bool reserveAll () noexcept
522 {
523 static_assert (I < sizeof...(Sizes), "pool index out of range");
524 return std::get<I> (_pools).reserveAll ();
525 }
526
530 template <size_t I = 0>
531 void releaseAll () noexcept
532 {
533 static_assert (I < sizeof...(Sizes), "pool index out of range");
534 std::get<I> (_pools).releaseAll ();
535 }
536
537#ifdef JOIN_HAS_NUMA
543 int mbind (int numa) const noexcept
544 {
545 return _backend.mbind (numa);
546 }
547#endif
548
553 int mlock () const noexcept
554 {
555 return _backend.mlock ();
556 }
557
558 private:
563 static std::array<size_t, sizeof...(Sizes)> makeOffsets ()
564 {
565 size_t totals[] = {BasicPool<Count, Sizes>::_total...};
566 std::array<size_t, sizeof...(Sizes)> offsets{};
567 for (size_t i = 1; i < sizeof...(Sizes); ++i)
568 {
569 offsets[i] = offsets[i - 1] + totals[i - 1];
570 }
571 return offsets;
572 }
573
579 template <size_t... Is>
580 std::tuple<BasicPool<Count, Sizes>...> makePools (std::index_sequence<Is...>) noexcept
581 {
582 auto offsets = makeOffsets ();
583 return std::tuple<BasicPool<Count, Sizes>...>{
584 BasicPool<Count, Sizes> (static_cast<char*> (_backend.get ()) + offsets[Is])...};
585 }
586
590 template <size_t I>
591 typename std::enable_if<(I < sizeof...(Sizes)), void*>::type allocateImplem (size_t size) noexcept
592 {
593 auto& pool = std::get<I> (_pools);
594 if (size <= pool._size)
595 {
596 void* p = pool.pop ();
597 if (p != nullptr)
598 {
599 return p;
600 }
601 }
602 return allocateImplem<I + 1> (size);
603 }
604
608 template <size_t I>
609 typename std::enable_if<(I >= sizeof...(Sizes)), void*>::type allocateImplem (size_t) noexcept
610 {
611 return nullptr;
612 }
613
617 template <size_t I>
618 typename std::enable_if<(I < sizeof...(Sizes)), void*>::type tryAllocateImplem (size_t size) noexcept
619 {
620 auto& pool = std::get<I> (_pools);
621 if (size <= pool._size)
622 {
623 return pool.pop ();
624 }
625 return tryAllocateImplem<I + 1> (size);
626 }
627
631 template <size_t I>
632 typename std::enable_if<(I >= sizeof...(Sizes)), void*>::type tryAllocateImplem (size_t) noexcept
633 {
634 return nullptr;
635 }
636
640 template <size_t I>
641 typename std::enable_if<(I < sizeof...(Sizes))>::type deallocateImplem (void* p) noexcept
642 {
643 auto& pool = std::get<I> (_pools);
644 if (pool.owns (p))
645 {
646 pool.push (p);
647 return;
648 }
649 deallocateImplem<I + 1> (p);
650 }
651
655 template <size_t I>
656 typename std::enable_if<(I >= sizeof...(Sizes))>::type deallocateImplem (void*) noexcept
657 {
658 }
659
661 Backend _backend;
662
664 std::tuple<BasicPool<Count, Sizes>...> _pools;
665 };
666}
667
668#endif
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