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 (std::max_align_t) BasicChunk
69 {
70 static_assert ((Size & (Size - 1)) == 0, "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 for (uint32_t i = 0; i < _count - 1; ++i)
141 {
142 _segment->_chunks[i]._next = i + 1;
143 }
144 _segment->_chunks[_count - 1]._next = Segment::NULL_IDX;
145
146 TaggedIndex head;
147 head.idx = 0;
148 head.gen = 0;
149 _segment->_head.store (head.raw, std::memory_order_relaxed);
150
151 _segment->_magic.store (Segment::MAGIC, std::memory_order_release);
152 }
153 else
154 {
155 Backoff backoff;
156 while (_segment->_magic.load (std::memory_order_acquire) != Segment::MAGIC)
157 {
158 backoff ();
159 }
160 }
161 }
162
167 BasicPool (const BasicPool& other) = delete;
168
173 BasicPool& operator= (const BasicPool& other) = delete;
174
179 BasicPool (BasicPool&& other) noexcept
180 : _segment (other._segment)
181 {
182 other._segment = nullptr;
183 }
184
190 BasicPool& operator= (BasicPool&& other) noexcept
191 {
192 _segment = other._segment;
193 other._segment = nullptr;
194 return *this;
195 }
196
200 ~BasicPool () = default;
201
206 void* pop () noexcept
207 {
208 TaggedIndex cur, next;
209 cur.raw = _segment->_head.load (std::memory_order_acquire);
210
211 for (;;)
212 {
213 if (JOIN_UNLIKELY (cur.idx == Segment::NULL_IDX))
214 {
215 return nullptr;
216 }
217
218 next.idx = _segment->_chunks[cur.idx]._next;
219 next.gen = cur.gen + 1;
220
221 if (JOIN_LIKELY (_segment->_head.compare_exchange_weak (cur.raw, next.raw, std::memory_order_acq_rel,
222 std::memory_order_acquire)))
223 {
224 return &_segment->_chunks[cur.idx];
225 }
226 }
227 }
228
233 void push (void* p) noexcept
234 {
235 TaggedIndex cur, next;
236 next.idx = static_cast<uint32_t> (reinterpret_cast<Chunk*> (p) - _segment->_chunks);
237 cur.raw = _segment->_head.load (std::memory_order_relaxed);
238
239 for (;;)
240 {
241 _segment->_chunks[next.idx]._next = cur.idx;
242 next.gen = cur.gen + 1;
243
244 if (JOIN_LIKELY (_segment->_head.compare_exchange_weak (cur.raw, next.raw, std::memory_order_release,
245 std::memory_order_relaxed)))
246 {
247 return;
248 }
249 }
250 }
251
257 bool owns (void* p) const noexcept
258 {
259 auto base = reinterpret_cast<std::uintptr_t> (_segment->_chunks);
260 auto end = base + (_count * _stride);
261 auto ptr = reinterpret_cast<std::uintptr_t> (p);
262 return ((ptr >= base) && (ptr < end));
263 }
264
270 uint32_t getIndex (void* p) const noexcept
271 {
272 return static_cast<uint32_t> (reinterpret_cast<Chunk*> (p) - _segment->_chunks);
273 }
274
280 void* getPtr (uint32_t idx) const noexcept
281 {
282 return &_segment->_chunks[idx];
283 }
284
286 Segment* _segment = nullptr;
287 };
288
292 template <size_t Count, size_t... Sizes>
293 struct TotalSize;
294
298 template <size_t Count, size_t First, size_t... Rest>
299 struct TotalSize<Count, First, Rest...>
300 {
301 static constexpr size_t value = BasicPool<Count, First>::_total + TotalSize<Count, Rest...>::value;
302 };
303
307 template <size_t Count, size_t Last>
308 struct TotalSize<Count, Last>
309 {
310 static constexpr size_t value = BasicPool<Count, Last>::_total;
311 };
312
316 template <size_t... Sizes>
317 struct Sorted;
318
322 template <size_t First, size_t Second, size_t... Rest>
323 struct Sorted<First, Second, Rest...>
324 : std::integral_constant<bool, (First <= Second) && Sorted<Second, Rest...>::value>
325 {
326 };
327
331 template <size_t Last>
332 struct Sorted<Last> : std::true_type
333 {
334 };
335
339 template <>
340 struct Sorted<> : std::true_type
341 {
342 };
343
347 template <typename Backend, size_t Count, size_t... Sizes>
348 class BasicArena
349 {
350 static_assert (sizeof...(Sizes) > 0, "arena must have at least one pool size");
351 static_assert (Sorted<Sizes...>::value, "pool sizes must be provided in ascending order");
352
353 public:
355 static constexpr size_t _total = TotalSize<Count, Sizes...>::value;
356
361 template <typename... Args>
362 BasicArena (Args&&... args)
363 : _backend (_total, std::forward<Args> (args)...)
364 , _pools (makePools (std::make_index_sequence<sizeof...(Sizes)>{}))
365 {
366 }
367
372 BasicArena (const BasicArena& other) = delete;
373
378 BasicArena& operator= (const BasicArena& other) = delete;
379
384 BasicArena (BasicArena&& other) noexcept
385 : _backend (std::move (other._backend))
386 , _pools (std::move (other._pools))
387 {
388 }
389
395 BasicArena& operator= (BasicArena&& other) noexcept
396 {
397 _backend = std::move (other._backend);
398 _pools = std::move (other._pools);
399 return *this;
400 }
401
405 ~BasicArena () = default;
406
412 void* allocate (size_t size) noexcept
413 {
414 return allocateImplem<0> (size);
415 }
416
422 void* tryAllocate (size_t size) noexcept
423 {
424 return tryAllocateImplem<0> (size);
425 }
426
431 void deallocate (void* p) noexcept
432 {
433 if (p == nullptr)
434 {
435 return;
436 }
437 deallocateImplem<0> (p);
438 }
439
440#ifdef JOIN_HAS_NUMA
446 int mbind (int numa) const noexcept
447 {
448 return _backend.mbind (numa);
449 }
450#endif
451
456 int mlock () const noexcept
457 {
458 return _backend.mlock ();
459 }
460
461 private:
466 static std::array<size_t, sizeof...(Sizes)> makeOffsets ()
467 {
468 size_t totals[] = {BasicPool<Count, Sizes>::_total...};
469 std::array<size_t, sizeof...(Sizes)> offsets{};
470 for (size_t i = 1; i < sizeof...(Sizes); ++i)
471 {
472 offsets[i] = offsets[i - 1] + totals[i - 1];
473 }
474 return offsets;
475 }
476
482 template <size_t... Is>
483 std::tuple<BasicPool<Count, Sizes>...> makePools (std::index_sequence<Is...>) noexcept
484 {
485 auto offsets = makeOffsets ();
486 return std::tuple<BasicPool<Count, Sizes>...>{
487 BasicPool<Count, Sizes> (static_cast<char*> (_backend.get ()) + offsets[Is])...};
488 }
489
493 template <size_t I>
494 typename std::enable_if<(I < sizeof...(Sizes)), void*>::type allocateImplem (size_t size) noexcept
495 {
496 auto& pool = std::get<I> (_pools);
497 if (size <= pool._size)
498 {
499 void* p = pool.pop ();
500 if (p != nullptr)
501 {
502 return p;
503 }
504 }
505 return allocateImplem<I + 1> (size);
506 }
507
511 template <size_t I>
512 typename std::enable_if<(I >= sizeof...(Sizes)), void*>::type allocateImplem (size_t) noexcept
513 {
514 return nullptr;
515 }
516
520 template <size_t I>
521 typename std::enable_if<(I < sizeof...(Sizes)), void*>::type tryAllocateImplem (size_t size) noexcept
522 {
523 auto& pool = std::get<I> (_pools);
524 if (size <= pool._size)
525 {
526 return pool.pop ();
527 }
528 return tryAllocateImplem<I + 1> (size);
529 }
530
534 template <size_t I>
535 typename std::enable_if<(I >= sizeof...(Sizes)), void*>::type tryAllocateImplem (size_t) noexcept
536 {
537 return nullptr;
538 }
539
543 template <size_t I>
544 typename std::enable_if<(I < sizeof...(Sizes))>::type deallocateImplem (void* p) noexcept
545 {
546 auto& pool = std::get<I> (_pools);
547 if (pool.owns (p))
548 {
549 pool.push (p);
550 return;
551 }
552 deallocateImplem<I + 1> (p);
553 }
554
558 template <size_t I>
559 typename std::enable_if<(I >= sizeof...(Sizes))>::type deallocateImplem (void*) noexcept
560 {
561 }
562
564 Backend _backend;
565
567 std::tuple<BasicPool<Count, Sizes>...> _pools;
568 };
569}
570
571#endif
adaptive backoff strategy for busy-wait loops.
Definition backoff.hpp:45
memory arena owning backend and managing one or more pools.
Definition memory.hpp:53
~BasicArena()=default
destroy instance.
BasicArena(const BasicArena &other)=delete
copy constructor.
int mlock() const noexcept
lock memory in RAM.
Definition allocator.hpp:456
void * allocate(size_t size) noexcept
allocate memory from the first pool that fits (promotes if exhausted).
Definition allocator.hpp:412
void * tryAllocate(size_t size) noexcept
allocate memory from the exact pool that fits, without promotion.
Definition allocator.hpp:422
BasicArena(BasicArena &&other) noexcept
move constructor.
Definition allocator.hpp:384
void deallocate(void *p) noexcept
return memory to the appropriate pool.
Definition allocator.hpp:431
Definition acceptor.hpp:32
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:233
uint32_t getIndex(void *p) const noexcept
get the index of a chunk in the pool.
Definition allocator.hpp:270
void * getPtr(uint32_t idx) const noexcept
get the pointer to a chunk by index.
Definition allocator.hpp:280
void * pop() noexcept
pop a chunk from the pool.
Definition allocator.hpp:206
BasicPool(const BasicPool &other)=delete
copy constructor.
BasicPool(BasicPool &&other) noexcept
move constructor.
Definition allocator.hpp:179
bool owns(void *p) const noexcept
check if the pointer belongs to this pool.
Definition allocator.hpp:257
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:317
total size computation.
Definition allocator.hpp:293
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:46
#define JOIN_UNLIKELY(x)
Definition utils.hpp:47