join 1.0
lightweight network framework library
Loading...
Searching...
No Matches
bump_allocator.hpp
Go to the documentation of this file.
1
25#ifndef JOIN_CORE_BUMP_ALLOCATOR_HPP
26#define JOIN_CORE_BUMP_ALLOCATOR_HPP
27
28// libjoin.
29#include <join/memory.hpp>
30#include <join/utils.hpp>
31
32// C++.
33#include <type_traits>
34#include <algorithm>
35#include <utility>
36#include <atomic>
37#include <new>
38
39// C.
40#include <cstdint>
41#include <cstddef>
42
43namespace join
44{
48 template <typename Backend, size_t Capacity, typename SyncPolicy>
49 class BasicBumpAllocator
50 {
51 static_assert (Capacity > 0, "capacity must be at least 1");
52
53 public:
59 template <typename... Args>
60 BasicBumpAllocator (Args&&... args)
61 : _backend (Capacity, std::forward<Args> (args)...)
62 , _base (static_cast<char*> (_backend.get ()))
63 {
64 }
65
70 BasicBumpAllocator (const BasicBumpAllocator& other) = delete;
71
77
83 : _backend (std::move (other._backend))
84 , _previous (other._previous)
85 , _base (other._base)
86 , _capacity (other._capacity)
87 , _cursor (std::move (other._cursor))
88 {
89 other._previous = nullptr;
90 other._base = nullptr;
91 other._capacity = Capacity;
92 other._cursor.store (0);
93 }
94
101 {
102 if (this != &other)
103 {
104 release ();
105
106 _backend = std::move (other._backend);
107 _previous = other._previous;
108 _base = other._base;
109 _capacity = other._capacity;
110 _cursor = std::move (other._cursor);
111
112 other._previous = nullptr;
113 other._base = nullptr;
114 other._capacity = Capacity;
115 other._cursor.store (0);
116 }
117
118 return *this;
119 }
120
125 {
126 release ();
127 }
128
134 void* allocate (size_t size) noexcept
135 {
136 const size_t bits = size | alignof (std::max_align_t);
137 return allocate (size, bits & (~bits + 1));
138 }
139
146 void* allocate (size_t size, size_t alignment) noexcept
147 {
148 if (JOIN_UNLIKELY ((_base == nullptr) || !isPow2 (alignment)))
149 {
150 return nullptr;
151 }
152
153 const uintptr_t base = reinterpret_cast<uintptr_t> (_base);
154 uint64_t current = _cursor.load ();
155 uint64_t offset;
156
157 do
158 {
159 offset = ((base + current + alignment - 1) & ~(alignment - 1)) - base;
160
161 if (JOIN_UNLIKELY ((offset > _capacity) || (size > _capacity - offset)))
162 {
163 if constexpr (std::is_same_v<SyncPolicy, BumpPlain>)
164 {
165 return grow (size, alignment);
166 }
167 return nullptr;
168 }
169 }
170 while (!_cursor.advance (current, offset + size));
171
172 return _base + offset;
173 }
174
180 void deallocate ([[maybe_unused]] void* p, [[maybe_unused]] size_t size) noexcept
181 {
182 }
183
187 void reset () noexcept
188 {
189 release ();
190 _cursor.store (0);
191 }
192
197 static constexpr size_t capacity () noexcept
198 {
199 return Capacity;
200 }
201
206 size_t used () const noexcept
207 {
208 return _cursor.load ();
209 }
210
215 bool hasBackend () const noexcept
216 {
217 return _backend.mapped ();
218 }
219
220#ifdef JOIN_HAS_NUMA
226 int mbind (int numa) const noexcept
227 {
228 return _backend.mbind (numa);
229 }
230#endif
231
236 int mlock () const noexcept
237 {
238 return _backend.mlock ();
239 }
240
241 private:
248 void* grow (size_t size, size_t alignment) noexcept
249 {
250 if (JOIN_UNLIKELY (size + alignment + sizeof (BasicBumpAllocator) + alignof (BasicBumpAllocator) < size))
251 {
252 return nullptr;
253 }
254
255 try
256 {
257 const uint64_t capacity =
258 (std::max<uint64_t> (_capacity * 2, size + alignment) + alignof (BasicBumpAllocator) - 1) &
259 ~(alignof (BasicBumpAllocator) - 1);
260 Backend backend (capacity + sizeof (BasicBumpAllocator));
261 _previous =
262 ::new (static_cast<char*> (backend.get ()) + capacity) BasicBumpAllocator (std::move (*this));
263 _backend = std::move (backend);
264 _base = static_cast<char*> (_backend.get ());
265 _capacity = capacity;
266 _cursor.store (0);
267 }
268 catch (...)
269 {
270 return nullptr;
271 }
272 return allocate (size, alignment);
273 }
274
278 void release () noexcept
279 {
280 if (_previous != nullptr)
281 {
282 _previous->~BasicBumpAllocator ();
283 _previous = nullptr;
284 }
285 }
286
288 Backend _backend;
289
291 BasicBumpAllocator* _previous = nullptr;
292
294 char* _base = nullptr;
295
297 uint64_t _capacity = Capacity;
298
300 SyncPolicy _cursor;
301 };
302
307 {
308 public:
312 BumpAtomic () noexcept = default;
313
318 BumpAtomic (BumpAtomic&& other) noexcept
319 : _value (other._value.load (std::memory_order_relaxed))
320 {
321 }
322
329 {
330 _value.store (other._value.load (std::memory_order_relaxed), std::memory_order_relaxed);
331 return *this;
332 }
333
338 uint64_t load () const noexcept
339 {
340 return _value.load (std::memory_order_relaxed);
341 }
342
347 void store (uint64_t value) noexcept
348 {
349 _value.store (value, std::memory_order_relaxed);
350 }
351
358 bool advance (uint64_t& expected, uint64_t desired) noexcept
359 {
360 return _value.compare_exchange_weak (expected, desired, std::memory_order_relaxed);
361 }
362
363 private:
365 alignas (64) std::atomic_uint64_t _value{0};
366 };
367
372 {
373 public:
378 uint64_t load () const noexcept
379 {
380 return _value;
381 }
382
387 void store (uint64_t value) noexcept
388 {
389 _value = value;
390 }
391
398 bool advance ([[maybe_unused]] uint64_t& expected, uint64_t desired) noexcept
399 {
400 _value = desired;
401 return true;
402 }
403
404 private:
406 uint64_t _value = 0;
407 };
408}
409
410#endif
bump allocator owning its backends.
Definition memory.hpp:60
BasicBumpAllocator(BasicBumpAllocator &&other) noexcept
move constructor.
Definition bump_allocator.hpp:82
void * allocate(size_t size) noexcept
allocate memory at the cursor, aligned on the largest power of 2 dividing size, up to max_align_t.
Definition bump_allocator.hpp:134
static constexpr size_t capacity() noexcept
get the initial capacity.
Definition bump_allocator.hpp:197
void reset() noexcept
release all allocations at once.
Definition bump_allocator.hpp:187
void deallocate(void *p, size_t size) noexcept
release memory (no-op).
Definition bump_allocator.hpp:180
BasicBumpAllocator & operator=(const BasicBumpAllocator &other)=delete
copy assignment operator.
BasicBumpAllocator(const BasicBumpAllocator &other)=delete
copy constructor.
~BasicBumpAllocator()
destroy instance.
Definition bump_allocator.hpp:124
void * allocate(size_t size, size_t alignment) noexcept
allocate memory at the cursor with an explicit alignment.
Definition bump_allocator.hpp:146
size_t used() const noexcept
get the number of bytes consumed in the current block, alignment padding included.
Definition bump_allocator.hpp:206
BasicBumpAllocator(Args &&... args)
create instance.
Definition bump_allocator.hpp:60
bool hasBackend() const noexcept
check if the bump allocator still owns a memory region.
Definition bump_allocator.hpp:215
int mlock() const noexcept
lock the current block in RAM.
Definition bump_allocator.hpp:236
bump sync policy using a lock-free cursor.
Definition bump_allocator.hpp:307
BumpAtomic() noexcept=default
default constructor.
void store(uint64_t value) noexcept
store the cursor.
Definition bump_allocator.hpp:347
uint64_t load() const noexcept
load the cursor.
Definition bump_allocator.hpp:338
bool advance(uint64_t &expected, uint64_t desired) noexcept
advance the cursor.
Definition bump_allocator.hpp:358
BumpAtomic & operator=(BumpAtomic &&other) noexcept
move assignment operator.
Definition bump_allocator.hpp:328
bump sync policy using a plain cursor, single thread only.
Definition bump_allocator.hpp:372
uint64_t load() const noexcept
load the cursor.
Definition bump_allocator.hpp:378
bool advance(uint64_t &expected, uint64_t desired) noexcept
advance the cursor.
Definition bump_allocator.hpp:398
void store(uint64_t value) noexcept
store the cursor.
Definition bump_allocator.hpp:387
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
#define JOIN_UNLIKELY(x)
Definition utils.hpp:51