neoGFX
Cross-platform C++ app/game engine
Loading...
Searching...
No Matches
allocator.hpp
Go to the documentation of this file.
1// allocator.hpp
2/*
3 * Copyright (c) 2018, 2020 Leigh Johnston.
4 *
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are
9 * met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 *
14 * * Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * * Neither the name of Leigh Johnston nor the names of any
19 * other contributors to this software may be used to endorse or
20 * promote products derived from this software without specific prior
21 * written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
24 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
26 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34*/
35
36#pragma once
37
38#include <neolib/neolib.hpp>
39#include <memory>
40#include <type_traits>
41#include <ostream>
42#include <boost/pool/pool_alloc.hpp>
43#include <boost/pool/singleton_pool.hpp>
45
46namespace neolib
47{
48
49 template <typename T, std::size_t ChunkSize = 4096, bool Omega = false, std::size_t Instance = 0>
51 {
52 public:
53 typedef T value_type;
54 typedef T* pointer;
55 typedef T& reference;
56 typedef const T* const_pointer;
57 typedef const T& const_reference;
58 typedef std::size_t size_type;
59 typedef ptrdiff_t difference_type;
60 private:
61 typedef std::allocator<T> backup_allocator_t;
62
63 // implementation
64 private:
65 struct link { link* iNext; };
66
67 static constexpr std::size_t chunk_size()
68 {
69 if constexpr (ChunkSize > sizeof(T))
70 return ChunkSize;
71 else
72 return sizeof(T);
73 }
74
75 static constexpr std::size_t element_size()
76 {
77 return sizeof(value_type) < sizeof(link) ? sizeof(link) : sizeof(value_type);
78 }
79
80 struct chunk
81 {
82 alignas(T) char iMem[chunk_size()];
83 chunk* iNext;
84 };
85 class pool
86 {
87 public:
88 pool() : iChunks(nullptr), iHead(nullptr)
89 {
90 }
91 ~pool()
92 {
93 chunk* n = iChunks;
94 while (n)
95 {
96 chunk* p = n;
97 n = n->iNext;
98 delete p;
99 }
100 }
101 public:
102 void* allocate()
103 {
104 if (iHead == nullptr)
105 grow();
106 link* p = iHead;
107 if constexpr (!Omega)
108 iHead = p->iNext;
109 else if (reinterpret_cast<intptr_t>(iHead->iNext) == intptr_t{ -1 })
110 iHead = reinterpret_cast<link*>(reinterpret_cast<char*>(p) + element_size());
111 else
112 iHead = p->iNext;
113 return p;
114 }
115 void deallocate(void* aObject)
116 {
117 if constexpr (!Omega)
118 {
119 link* p = reinterpret_cast<link*>(aObject);
120 p->iNext = iHead;
121 iHead = p;
122 }
123 }
124 public:
125 void omega_recycle()
126 {
127 if constexpr (Omega)
128 {
129 for (chunk* n = iChunks; n != nullptr; n = n->iNext)
130 {
131 constexpr std::size_t nelem = chunk_size() / element_size();
132 char* start = n->iMem;
133 char* last = start + nelem * element_size();
134 std::memset(start, 0xFF, last - start);
135 reinterpret_cast<link*>(last - element_size())->iNext = (n->iNext != nullptr ? reinterpret_cast<link*>(n->iNext->iMem) : nullptr);
136 }
137 iHead = reinterpret_cast<link*>(iChunks->iMem);
138 }
139 }
140 template <typename CharT, typename Traits>
141 void info(std::basic_ostream<CharT, Traits>& aOutput)
142 {
143 uint32_t total = 0;
144 uint32_t pct = 0;
145 for (chunk* n = iChunks; n != nullptr; n = n->iNext)
146 {
147 ++total;
148 char* start = n->iMem;
149 char* last = start + chunk_size();
150 if (reinterpret_cast<char*>(iHead) >= start && reinterpret_cast<char*>(iHead) < last)
151 pct = static_cast<uint32_t>((reinterpret_cast<char*>(iHead) - start) * 100 / (last - start));
152 }
153 aOutput << "Number of chunks: " << total << std::endl;
154 if constexpr (Omega)
155 aOutput << "% utilization of last used chunk: " << pct << "%" << std::endl;
156 }
157 private:
158 void grow()
159 {
160 chunk* n = new chunk;
161 n->iNext = iChunks;
162 iChunks = n;
163
164 constexpr std::size_t nelem = chunk_size() / element_size();
165 char* start = n->iMem;
166 char* last = start + nelem * element_size();
167 if constexpr (!Omega)
168 for (char* p = start; p < last; p += element_size())
169 reinterpret_cast<link*>(p)->iNext = reinterpret_cast<link*>(p + element_size());
170 else
171 std::memset(start, 0xFF, last - start);
172
173 reinterpret_cast<link*>(last - element_size())->iNext = nullptr;
174 iHead = reinterpret_cast<link*>(start);
175 }
176 private:
177 chunk * iChunks;
178 link* iHead;
179 };
180
181 // construction
182 public:
184 {
185 }
186
190
191 template <typename U>
195
197 {
198 }
199
200 // operations
201 public:
202 static pointer allocate(size_type aCount = 1)
203 {
204 if (aCount == 1)
205 return reinterpret_cast<pointer>(get_pool().allocate());
206 else
207 return backup_allocator().allocate(aCount);
208 }
209
210 static void deallocate(pointer aObject, size_type aCount = 1)
211 {
212 if constexpr (!Omega)
213 {
214 if (aCount == 1)
215 get_pool().deallocate(aObject);
216 else
217 backup_allocator().deallocate(aObject, aCount);
218 }
219 }
220
221 static void construct(pointer aObject, const_reference val)
222 {
223 new (aObject) T(val);
224 }
225
226 template <typename... Args>
227 static void construct(pointer aObject, Args&&... aArguments)
228 {
229 new (aObject) T(std::forward<Args>(aArguments)...);
230 }
231
232 static void destroy(pointer aObject)
233 {
234 if constexpr (!Omega)
235 aObject->~T();
236 }
237
238 static void omega_recycle()
239 {
240 get_pool().omega_recycle();
241 }
242
243 template <typename CharT, typename Traits>
244 static void info(std::basic_ostream<CharT, Traits>& aOutput)
245 {
246 get_pool().info(aOutput);
247 }
248
249 template <typename U>
254
255 // this should really return 1 but popular implementations assume otherwise
256 size_type max_size() const { return std::allocator<T>().max_size(); }
257
258 bool operator==(const neo_pool_allocator&) const { return true; }
259 bool operator!=(const neo_pool_allocator&) const { return false; }
260
261 private:
262 static backup_allocator_t& backup_allocator()
263 {
264 static backup_allocator_t sBackupAllocator;
265 return sBackupAllocator;
266 }
267 static pool& get_pool()
268 {
269 static pool sPool;
270 return sPool;
271 }
272 };
273
274 template <typename T, std::size_t N, std::size_t Instance = 0>
276 {
277 public:
278 typedef T value_type;
279 typedef T* pointer;
280 typedef T& reference;
281 typedef const T* const_pointer;
282 typedef const T& const_reference;
283 typedef std::size_t size_type;
284 typedef ptrdiff_t difference_type;
285
286 // implementation
287 private:
288 struct link { link* iNext; };
289 struct block
290 {
291 size_type iElementSize;
292 union
293 {
294 union
295 {
296 alignas(T) char a[sizeof(T)];
297 alignas(link) char b[sizeof(link)];
298 } iBuffer[N];
299 } iBuffer;
300 char* iMem;
301 link* iHead;
302 block() : iElementSize(sizeof(T) < sizeof(link) ? sizeof(link) : sizeof(T)), iMem(reinterpret_cast<char*>(iBuffer.iBuffer)), iHead(reinterpret_cast<link*>(iMem)) { init(); }
303 void init()
304 {
305 char* start = iMem;
306 char* last = &start[(N - 1) * iElementSize];
307 for (char* p = start; p < last; p += iElementSize)
308 reinterpret_cast<link*>(p)->iNext = reinterpret_cast<link*>(p + iElementSize);
309 reinterpret_cast<link*>(last)->iNext = nullptr;
310 }
311 void* allocate()
312 {
313 if (iHead == nullptr)
314 throw std::bad_alloc();
315 link* p = iHead;
316 iHead = p->iNext;
317 return p;
318 }
319 void deallocate(void* aObject)
320 {
321 link* p = reinterpret_cast<link*>(aObject);
322 p->iNext = iHead;
323 iHead = p;
324 }
325 };
326
327 // construction
328 public:
331 template <typename U>
334
335 // operations
336 public:
337 T* allocate(size_type aCount = 1)
338 {
339 if (aCount != 1)
340 throw std::bad_alloc();
341 return reinterpret_cast<T*>(get_block().allocate());
342 }
343
344 void deallocate(T* aObject, size_type aCount = 1)
345 {
346 if (aCount != 1)
347 throw std::logic_error("neolib::reserve_allocator::deallocate");
348 get_block().deallocate(aObject);
349 }
350
352 {
353 new (aObject) T(val);
354 }
355
356 void destroy(pointer aObject)
357 {
358 aObject->~T();
359 }
360
361 template <typename U>
366
367 // this should really return 1 but popular implementations assume otherwise
368 size_type max_size() const { return std::allocator<T>().max_size(); }
369
370 bool operator==(const reserve_allocator&) const { return true; }
371 bool operator!=(const reserve_allocator&) const { return false; }
372
373 private:
374 static block& get_block()
375 {
376 static block sBlock;
377 return sBlock;
378 }
379 };
380
381 template <typename T, std::size_t SmallBufferSize = 8u>
383
384 template <typename T, typename R>
390
391 template <typename T, std::size_t SmallBufferSize>
393 {
394 typedef T value_type;
395 typedef std::aligned_storage_t<sizeof(value_type)* SmallBufferSize> buffer_storage_t;
398 small_buffer() : allocated{ false } {}
399 small_buffer(const small_buffer&) : allocated{ false } {}
400 small_buffer& operator=(const small_buffer&) { return *this; }
401 };
402
403 template <typename T, typename R, std::size_t SmallBufferSize>
404 class basic_small_buffer_allocator<small_buffer_allocator_types<T, R>, SmallBufferSize> : public std::allocator<R>
405 {
407 template <typename, std::size_t>
409 public:
410 struct no_small_buffer : std::logic_error { no_small_buffer() : std::logic_error("neolib::basic_small_buffer_allocator::no_small_buffer") {} };
411 public:
414 typedef std::false_type is_always_equal;
415 template<class U> struct rebind { typedef basic_small_buffer_allocator<small_buffer_allocator_types<T, U>, SmallBufferSize> other; };
416 public:
418 typedef R value_type;
419 typedef std::allocator<value_type> default_allocator_type;
420 typedef typename std::allocator_traits<default_allocator_type>::pointer pointer;
422 public:
425 iBuffer{ nullptr }
426 {
427 }
430 iBuffer{ &aBuffer }
431 {
432 }
434 default_allocator_type{ aOther },
435 iBuffer{ aOther.iBuffer }
436 {
437 }
439 default_allocator_type(std::move(aOther)),
440 iBuffer{ nullptr }
441 {
442 }
443 template <typename U>
445 default_allocator_type{ aOther },
446 iBuffer{ nullptr }
447 {
448 }
449 template <typename U>
451 default_allocator_type(std::move(aOther)),
452 iBuffer{ nullptr }
453 {
454 }
455 public:
457 {
458 iBuffer = aOther.iBuffer;
459 return *this;
460 }
462 {
463 iBuffer = nullptr;
464 return *this;
465 }
466 template <typename U>
468 {
469 iBuffer = nullptr;
470 return *this;
471 }
472 template <typename U>
474 {
475 iBuffer = nullptr;
476 return *this;
477 }
478 public:
479 bool operator==(const basic_small_buffer_allocator& aOther) const
480 {
481 return false;
482 }
483 bool operator!=(const basic_small_buffer_allocator& aOther) const
484 {
485 return true;
486 }
487 public:
488 pointer allocate(std::size_t n)
489 {
490 return allocate(n, nullptr);
491 }
492 pointer allocate(std::size_t n, const void*)
493 {
494 if constexpr (std::is_same_v<value_type, controlled_value_type>)
495 {
496 if (n <= SmallBufferSize && is_buffer_available())
497 {
498 buffer().allocated = true;
499 return reinterpret_cast<pointer>(&buffer().storage);
500 }
501 else
502 return default_allocator_type::allocate(n);
503 }
504 else
505 return default_allocator_type::allocate(n);
506 }
507 void deallocate(pointer p, std::size_t n)
508 {
509 if constexpr (std::is_same_v<value_type, controlled_value_type>)
510 {
511 if (is_buffer_used() && p == reinterpret_cast<pointer>(&buffer().storage))
512 buffer().allocated = false;
513 else
514 default_allocator_type::deallocate(p, n);
515 }
516 else
517 default_allocator_type::deallocate(p, n);
518 }
519 public:
520 bool has_buffer() const
521 {
522 return iBuffer != nullptr;
523 }
525 {
526 return has_buffer() && !buffer().allocated;
527 }
528 bool is_buffer_used() const
529 {
530 return has_buffer() && buffer().allocated;
531 }
533 {
534 if (has_buffer())
535 return *iBuffer;
536 throw no_small_buffer();
537 }
539 {
540 return const_cast<small_buffer_type&>(to_const(*this).buffer());
541 }
542 private:
543 small_buffer_type* iBuffer;
544 };
545
546 template <typename T, typename U, std::size_t SmallBufferSize>
548 {
549 return false;
550 }
551
552 template <typename T, typename U, std::size_t SmallBufferSize>
554 {
555 return true;
556 }
557
558 template <typename T, std::size_t SmallBufferSize = 8u>
560
561 // WARNING: Omega allocator doesn't free chunks and doesn't call element destructors on deallocation; use only when pathological performance is required.
562 template <typename T, std::size_t ChunkSize = 1 * 1024 * 1024>
564
565 template <typename T, unsigned NextSize = 32>
566 using thread_safe_pool_allocator = boost::pool_allocator<T, boost::default_user_allocator_new_delete, boost::details::pool::default_mutex, NextSize>;
567 template <typename T, unsigned NextSize = 32>
568 using pool_allocator = boost::pool_allocator<T, boost::default_user_allocator_new_delete, boost::details::pool::null_mutex, NextSize>;
569
570 template <typename T, unsigned NextSize = 32>
571 using thread_safe_fast_pool_allocator = boost::fast_pool_allocator<T, boost::default_user_allocator_new_delete, boost::details::pool::default_mutex, NextSize>;
572 template <typename T, unsigned NextSize = 32>
573 using fast_pool_allocator = boost::fast_pool_allocator<T, boost::default_user_allocator_new_delete, boost::details::pool::null_mutex, NextSize>;
574}
basic_small_buffer_allocator & operator=(const basic_small_buffer_allocator &aOther)
basic_small_buffer_allocator(const basic_small_buffer_allocator< U, SmallBufferSize > &aOther)
basic_small_buffer_allocator & operator=(basic_small_buffer_allocator &&aOther)
basic_small_buffer_allocator(const basic_small_buffer_allocator< U, SmallBufferSize > &&aOther)
basic_small_buffer_allocator & operator=(basic_small_buffer_allocator< U, SmallBufferSize > &&aOther)
basic_small_buffer_allocator & operator=(const basic_small_buffer_allocator< U, SmallBufferSize > &aOther)
neo_pool_allocator(const neo_pool_allocator< U, ChunkSize, Omega, Instance > &)
static void construct(pointer aObject, const_reference val)
neo_pool_allocator(const neo_pool_allocator &)
bool operator==(const neo_pool_allocator &) const
static void deallocate(pointer aObject, size_type aCount=1)
static void destroy(pointer aObject)
static void construct(pointer aObject, Args &&... aArguments)
size_type max_size() const
static void info(std::basic_ostream< CharT, Traits > &aOutput)
static pointer allocate(size_type aCount=1)
bool operator!=(const neo_pool_allocator &) const
void deallocate(T *aObject, size_type aCount=1)
reserve_allocator(const reserve_allocator &rhs)
bool operator!=(const reserve_allocator &) const
T * allocate(size_type aCount=1)
bool operator==(const reserve_allocator &) const
reserve_allocator(const reserve_allocator< U, N, Instance > &rhs)
size_type max_size() const
void construct(pointer aObject, const_reference val)
void destroy(pointer aObject)
basic_length< T > pct(T aValue)
Definition units.hpp:738
boost::pool_allocator< T, boost::default_user_allocator_new_delete, boost::details::pool::default_mutex, NextSize > thread_safe_pool_allocator
boost::fast_pool_allocator< T, boost::default_user_allocator_new_delete, boost::details::pool::default_mutex, NextSize > thread_safe_fast_pool_allocator
to_const_reference_t< T > to_const(T &&object)
Definition neolib.hpp:113
boost::fast_pool_allocator< T, boost::default_user_allocator_new_delete, boost::details::pool::null_mutex, NextSize > fast_pool_allocator
neo_pool_allocator< T, ChunkSize, true > omega_pool_allocator
boost::pool_allocator< T, boost::default_user_allocator_new_delete, boost::details::pool::null_mutex, NextSize > pool_allocator
Definition plf_hive.h:79
basic_small_buffer_allocator< small_buffer_allocator_types< T, U >, SmallBufferSize > other
neo_pool_allocator< U, ChunkSize, Omega, Instance > other
reserve_allocator< U, N, Instance > other
std::aligned_storage_t< sizeof(value_type) *SmallBufferSize > buffer_storage_t
buffer_storage_t storage
small_buffer & operator=(const small_buffer &)
small_buffer(const small_buffer &)