Anklang-0.3.0.dev712+gdc4e642f anklang-0.3.0.dev712+gdc4e642f
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
loft.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "loft.hh"
3#include "platform.hh"
4#include "atomics.hh"
5#include "randomhash.hh"
6#include "main.hh"
7#include "internal.hh"
8#include "testing.hh"
9#include <sys/mman.h>
10#include <wchar.h>
11
12#define MDEBUG(...) Ase::debug ("memory", __VA_ARGS__)
13#define VDEBUG(...) Ase::debug ("memory", __VA_ARGS__) // more verbose
14
15#define MEM_ALIGN(addr, ALIGNMENT) (ALIGNMENT * size_t ((size_t (addr) + ALIGNMENT - 1) / ALIGNMENT))
16
17namespace Ase::Loft {
18
19// == LoftConfig ==
20static constexpr size_t MINIMUM_HUGEPAGE = 2 * 1024 * 1024;
21static std::atomic<Flags> config_flags = Flags (0);
22static std::atomic<size_t> config_watermark = MINIMUM_HUGEPAGE;
23static std::atomic<size_t> config_preallocate = 2 * MINIMUM_HUGEPAGE;
24static std::atomic<std::function<void()>*> config_lowmem_cb = nullptr;
25static std::atomic<size_t> config_lowmem_notified = 0;
26static std::atomic<std::function<void(size_t, size_t)>*> config_willgrow = nullptr;
27
28struct ArenaSpan { uintptr_t addr, offset, size; };
30
31// == BumpAllocator ==
41 void* bump_alloc (size_t size);
42 size_t grow_spans (size_t needed, bool preallocating);
43 size_t free_block () const;
44 void list_arenas (ArenaList &arenas);
45 size_t totalmem () const { return totalmem_; }
46private:
47 char* memmap (size_t size, bool growsup);
48 char* remap (void *addr, size_t oldsize, size_t size);
49 struct MMSpan {
50 char *mmstart = nullptr;
51 boost::atomic<uintptr_t> offset = 0;
52 boost::atomic<uintptr_t> mmsize = 0;
53 boost::atomic<MMSpan*> next = nullptr;
54 };
55 boost::atomic<size_t> totalmem_ = 0;
56 boost::atomic<MMSpan*> spans_ = nullptr;
57 std::mutex mutex_;
58};
59
60void
61BumpAllocator::list_arenas (ArenaList &arenas)
62{
63 for (MMSpan *lspan = spans_; lspan; lspan = lspan->next)
64 arenas.push_back ({ uintptr_t (lspan->mmstart), lspan->offset, lspan->mmsize });
65}
66
67size_t
68BumpAllocator::free_block () const
69{
70 // find the last span
71 MMSpan *lspan = spans_;
72 while (lspan && lspan->next)
73 lspan = lspan->next;
74 return_unless (lspan, 0);
75 // figure largest consecutive available block
76 return lspan->mmsize - lspan->offset;
77}
78
79size_t
80BumpAllocator::grow_spans (size_t needed, bool preallocating)
81{
82 const size_t entry_total = totalmem_;
83 std::lock_guard<std::mutex> locker (mutex_);
84 if (entry_total < totalmem_)
85 return 0; // another thread grew the spans meanwhile
86 static const size_t PAGESIZE = sysconf (_SC_PAGESIZE);
87 size_t totalmem = 0;
88 // try growing the last span
89 MMSpan *lspan = spans_;
90 while (lspan && lspan->next)
91 {
92 totalmem += lspan->mmsize;
93 lspan = lspan->next;
94 }
95 if (lspan)
96 {
97 const size_t oldsize = lspan->mmsize;
98 size_t newsize = oldsize * 2;
99 while (newsize < needed)
100 newsize *= 2;
101 char *mmstart = remap (lspan->mmstart, oldsize, newsize);
102 if (mmstart == lspan->mmstart)
103 {
104 if (config_flags & PREFAULT_PAGES)
105 for (size_t i = oldsize; i < newsize; i += PAGESIZE)
106 mmstart[i] = 1; // prefault pages
107 lspan->mmsize = newsize;
108 totalmem_ = totalmem + newsize;
109 VDEBUG ("%s:%u: new-total=%dM: true\n", __func__, __LINE__, totalmem_ / (1024 * 1024));
110 return newsize - oldsize;
111 }
112 }
113 // allocate new span
114 size_t mmsize = MINIMUM_HUGEPAGE;
115 while (mmsize < needed)
116 mmsize *= 2;
117 mmsize = MEM_ALIGN (mmsize, MINIMUM_HUGEPAGE);
118 char *mmstart = memmap (mmsize, !lspan);
119 if (!mmstart)
120 {
121 VDEBUG ("%s:%u: false\n", __func__, __LINE__);
122 return 0;
123 }
124 for (size_t i = 0; i < mmsize; i += PAGESIZE)
125 mmstart[i] = 1; // prefault pages
126 MMSpan *nspan = new (mmstart) MMSpan(); // TODO: move MMSpan to malloc
127 nspan->offset = MEM_ALIGN (sizeof (MMSpan), 64);
128 nspan->mmstart = mmstart;
129 nspan->mmsize = mmsize;
130 if (lspan)
131 lspan->next = nspan;
132 else
133 spans_ = nspan;
134 totalmem_ = totalmem + nspan->mmsize;
135 VDEBUG ("%s:%u: new-total=%dM: true\n", __func__, __LINE__, totalmem_ / (1024 * 1024));
136 return mmsize;
137}
138
139char*
140BumpAllocator::remap (void *addr, size_t oldsize, size_t size)
141{
142 assert_return (MEM_ALIGN (size, MINIMUM_HUGEPAGE) == size, nullptr);
143
144 // remap, preserve start address
145 char *memory = (char*) mremap (addr, oldsize, size, /*flags*/ 0, addr);
146 if (memory == MAP_FAILED)
147 {
148 VDEBUG ("%s:%u: mremap: %p %d %d: %s\n", __func__, __LINE__, addr, oldsize, size, strerror (errno));
149 return nullptr;
150 }
151 VDEBUG ("%s:%u: mremap: %p %d %d: %p\n", __func__, __LINE__, addr, oldsize, size, memory);
152 if (madvise (memory, size, MADV_HUGEPAGE) < 0)
153 VDEBUG ("%s: madvise(%p,%u,MADV_HUGEPAGE) failed: %s\n", __func__, memory, size, strerror (errno));
154 return memory;
155}
156
157char*
158BumpAllocator::memmap (size_t size, bool growsup)
159{
160 assert_return (MEM_ALIGN (size, MINIMUM_HUGEPAGE) == size, nullptr);
161 const int PROTECTION = PROT_READ | PROT_WRITE;
162 const int PRIVANON = MAP_PRIVATE | MAP_ANONYMOUS;
163
164 const bool anon_hugepages = true;
165 const bool reserved_hugepages = false; // prevents growth
166
167 // probe for an address with some headroom for growth
168 void *addr = nullptr;
169 const size_t g1 = 1024 * 1024 * 1024;
170 const size_t gigabytes = sizeof (size_t) <= 4 ? 3 * g1 : 256 * g1;
171 for (size_t giga = gigabytes; giga >= 16 * 1024 * 1024; giga = giga >> 1)
172 if ((addr = mmap (nullptr, giga, 0 * PROTECTION, PRIVANON, -1, 0)) != MAP_FAILED)
173 {
174 VDEBUG ("%s:%d: addr-hint size=%uMB: %p\n", __FILE__, __LINE__, giga / (1024 * 1024), addr);
175 munmap (addr, giga);
176 break;
177 }
178 if (addr == MAP_FAILED)
179 addr = nullptr;
180
181 // try reserved hugepages for large allocations
182 char *memory;
183 if (reserved_hugepages)
184 {
185 memory = (char*) mmap (addr, size, PROTECTION, PRIVANON | MAP_HUGETLB, -1, 0);
186 if (memory != MAP_FAILED)
187 {
188 VDEBUG ("%s:%u: mmap: %p %d HUGETLB: %p\n", __func__, __LINE__, addr, size, memory);
189 return memory;
190 }
191 VDEBUG ("%s:%u: mmap: %p %d HUGETLB: %s\n", __func__, __LINE__, addr, size, strerror (errno));
192 }
193
194 // mmap without HUGETLB at first, then try MADV_HUGEPAGE
195 size_t areasize = size + MINIMUM_HUGEPAGE;
196 memory = (char*) mmap (addr, areasize, PROTECTION, PRIVANON, -1, 0);
197 if (memory == MAP_FAILED)
198 {
199 VDEBUG ("%s:%u: mmap: %p %d: %s\n", __func__, __LINE__, addr, areasize, strerror (errno));
200 return nullptr;
201 }
202 VDEBUG ("%s:%u: mmap: %p %d: %p\n", __func__, __LINE__, addr, areasize, memory);
203 // discard unaligned head
204 const uintptr_t start = uintptr_t (memory);
205 size_t extra = MEM_ALIGN (start, MINIMUM_HUGEPAGE) - start;
206 if (extra && munmap (memory, extra) != 0)
207 VDEBUG ("%s: munmap(%p,%u) failed: %s\n", __func__, memory, extra, strerror (errno));
208 memory += extra;
209 areasize -= extra;
210 // discard unaligned tail
211 extra = areasize - size;
212 areasize -= extra;
213 if (extra && munmap (memory + areasize, extra) != 0)
214 VDEBUG ("%s: munmap(%p,%u) failed: %s\n", __func__, memory + areasize, extra, strerror (errno));
215 assert_warn (areasize == size);
216 VDEBUG ("%s:%u: mmap page-align: size=%d at: %p\n", __func__, __LINE__, size, memory);
217
218 // linux/Documentation/admin-guide/mm/transhuge.rst
219 if (anon_hugepages && madvise (memory, size, MADV_HUGEPAGE) < 0)
220 VDEBUG ("%s: madvise(%p,%u,MADV_HUGEPAGE) failed: %s\n", __func__, memory, size, strerror (errno));
221 return memory;
222}
223
224void*
225BumpAllocator::bump_alloc (size_t size)
226{
227 assert_return (size && !(size & (64-1)), nullptr);
228 for (MMSpan *span = spans_; span; span = span->next)
229 {
230 uintptr_t nmark, omark = span->offset;
231 do // Note, `compare_exchange_weak == 0` does `omark := offset`
232 {
233 nmark = omark + size;
234 if (nmark > span->mmsize)
235 goto next_span;
236 }
237 while (!span->offset.compare_exchange_weak (omark, nmark));
238 // check for watermark notifications
239 if (!span->next && // only check watermark against last span
240 span->mmsize - span->offset < config_watermark)
241 {
242 // block further notifications *before* calling
243 if (0 == config_lowmem_notified++)
244 {
245 std::function<void()> *lowmem_cb = config_lowmem_cb;
246 if (lowmem_cb) // copy pointer to avoid calling null
247 (*lowmem_cb) ();
248 }
249 }
250 return span->mmstart + omark;
251 next_span: ;
252 }
253 // no span could satisfy the request
254 const size_t old_totalmem_ = totalmem_;
255 grow_spans (size, false);
256 if (config_willgrow) {
257 std::function<void(size_t, size_t)> *willgrow = config_willgrow;
258 (*willgrow) (old_totalmem_, size);
259 }
260 // retry
261 return bump_alloc (size);
262}
263
264// == LoftBuckets ==
265static constexpr unsigned SMALL_BLOCK_LIMIT = 8192; // use 64 byte stepping for blocks up to this size
266static constexpr unsigned SMALL_BLOCK_BUCKETS = SMALL_BLOCK_LIMIT / 64;
267static constexpr unsigned NUMBER_OF_POWER2_BUCKETS = sizeof (uintptr_t) * 8;
268static constexpr unsigned NUMBER_OF_BUCKETS = NUMBER_OF_POWER2_BUCKETS + SMALL_BLOCK_BUCKETS;
269
270// Get size of slices in bytes handled by this bucket
271static inline size_t
272bucket_size (unsigned index)
273{
274 if (index < NUMBER_OF_POWER2_BUCKETS)
275 return 1 << index;
276 return (index - NUMBER_OF_POWER2_BUCKETS + 1) * 64;
277}
278
279// Get bucket index for a particular memory slice size
280static inline unsigned
281bucket_index (unsigned long long n)
282{
283 n += 0 == n; // treat 0 like a 1
284 if (n <= SMALL_BLOCK_LIMIT)
285 return NUMBER_OF_POWER2_BUCKETS + (n - 1) / 64;
286 constexpr unsigned CLZLL_BITS = sizeof (0ull) * 8; // __builtin_clzll (0) is undefined
287 const unsigned upper_power2_shift = CLZLL_BITS - __builtin_clzll (n - 1);
288 return upper_power2_shift;
289}
290
301 explicit LoftBuckets (BumpAllocator &bumpallocator) : bump_allocator (bumpallocator) {}
302 LoftPtr<void> do_alloc (size_t size, size_t align);
303 void do_free (void *mem, size_t size);
304 size_t count (unsigned bucket_index, const ArenaList &arenas);
305private:
306 struct Block {
307 uintptr_t canary0 = CANARY0;
308 boost::atomic<Block*> intr_ptr_ = nullptr; // atomic intrusive pointer
309 };
310 static_assert (sizeof (Block) <= 64);
311 std::array<MpmcStack<Block>,NUMBER_OF_BUCKETS> buckets_ alignas (64);
312 enum : uintptr_t {
313 CANARY0 = 0xbe4da62f087c3519ull
314 };
315 bool maybeok (const Block *block, const ArenaList &arenas);
316public:
317 BumpAllocator &bump_allocator;
318};
319
321LoftBuckets::do_alloc (size_t size, size_t align)
322{
323 if (align > 64)
324 return nullptr; // alignment not supported
325 size += 0 == size; // treat 0 like a 1, malloc always yields a new pointer
326 const unsigned bindex = bucket_index (size);
327 if (bindex >= NUMBER_OF_BUCKETS)
328 return nullptr; // size not supported
329 const size_t bsize = bucket_size (bindex);
330 auto block = buckets_[bindex].pop();
331 if (block)
332 assert_warn (block->canary0 == CANARY0); // simple overwrite check
333 else
334 block = (Block*) bump_allocator.bump_alloc (bsize);
335 block->canary0 = 0;
336 void *mem = block;
337 return LoftPtr<void> (mem, { bsize });
338}
339
340void
341LoftBuckets::do_free (void *mem, size_t size)
342{
343 assert_return (mem);
344 const unsigned bindex = bucket_index (size);
345 assert_return (bindex < NUMBER_OF_BUCKETS);
346 const size_t bsize = bucket_size (bindex);
347 assert_return (bsize == size);
348 auto block = new (mem) Block();
349 buckets_[bindex].push (block);
350}
351
352bool
353LoftBuckets::maybeok (const Block *block, const ArenaList &arenas)
354{
355 // test if block meets 64 byte alignment constraint
356 const uintptr_t addr = uintptr_t (block);
357 if (addr & 63)
358 return false;
359 // test if block is within known arena
360 bool contained = false;
361 for (const auto &a : arenas)
362 if (addr >= a.addr && addr < a.addr + a.size)
363 {
364 contained = true;
365 break;
366 }
367 // if the block pointer may be dereferenced, test if it is likely free
368 if (contained && // may dereference
369 block->canary0 == CANARY0)
370 return true;
371 return false;
372}
373
374size_t
375LoftBuckets::count (unsigned bucket_index, const ArenaList &arenas)
376{
377 assert_return (bucket_index < NUMBER_OF_BUCKETS, 0);
378 size_t counter = 0;
379 // count buckets as long as they are *likely* within arenas
380 for (Block *block = buckets_[bucket_index].peek(); block; block = block->intr_ptr_.load())
381 if (maybeok (block, arenas))
382 counter++;
383 else
384 break; // may not dereference ->next
385 return counter;
386}
387
388} // Ase::Loft
389
390// == Ase API ==
391namespace Ase {
392using namespace Loft;
393
394static inline bool
395no_allocators()
396{
397 static std::atomic<int> no_allocators_ = -1;
398 if (ASE_UNLIKELY (no_allocators_ == -1))
399 {
400 const char *f = getenv ("ASE_DEBUG");
401 no_allocators_ = f && (strstr (f, ":no-allocators") || strstr (f, "no-allocators") == f);
402 }
403 return no_allocators_;
404}
405
406static LoftBuckets&
407the_pool()
408{
409 static LoftBuckets &loft_buckets = *([] () {
410 BumpAllocator *bump_allocator = new (aligned_alloc (64, MEM_ALIGN (sizeof (BumpAllocator), 64))) BumpAllocator();
411 LoftBuckets *loft_buckets = new (aligned_alloc (64, MEM_ALIGN (sizeof (LoftBuckets), 64))) LoftBuckets (*bump_allocator);
412 return loft_buckets;
413 }) ();
414 return loft_buckets;
415}
416
418loft_alloc (size_t size, size_t align)
419{
420 if (ASE_UNLIKELY (no_allocators()))
421 return { aligned_alloc (align, size), { size } };
422
423 LoftBuckets &pool = the_pool();
424 LoftPtr<void> ptr = pool.do_alloc (size, align);
425 return ptr;
426}
427
429loft_calloc (size_t nelem, size_t elemsize, size_t align)
430{
431 const bool overflows = elemsize > 1 && nelem > ~size_t (0) / elemsize;
432 if (overflows)
433 return {};
434 const size_t size = nelem * elemsize;
435 if (ASE_UNLIKELY (no_allocators()))
436 {
437 void *p = aligned_alloc (align, size);
438 if (p)
439 wmemset ((wchar_t*) p, 0, size / sizeof (wchar_t));
440 return { p, { size } };
441 }
442
443 LoftBuckets &pool = the_pool();
444 LoftPtr<void> ptr = pool.do_alloc (size, align);
445 wmemset ((wchar_t*) ptr.get(), 0, size / sizeof (wchar_t));
446 return ptr;
447}
448
449static char*
450align_forward (const char *const ptr, const size_t alignment) noexcept
451{
452 const uintptr_t uiptr = uintptr_t (ptr);
453 const uintptr_t alptr = (uiptr + size_t (alignment - 1)) & -alignment;
454 return reinterpret_cast<char*> (alptr);
455}
456
457struct BoundaryTag {
458 char *mem = nullptr;
459 size_t sz = 0;
460};
461
463void*
464Loft::AllocatorBase::loft_btalloc (const size_t size, const size_t type_align)
465{
466 if (ASE_UNLIKELY (no_allocators()))
467 return aligned_alloc (type_align, size);
468 size_t asize = size + sizeof (BoundaryTag);
469 const size_t minalign = std::max (size_t (64), std::max (2 * sizeof (void*), sizeof (BoundaryTag)));
470 assert_return (0 == (minalign & (minalign - 1)), {}); // require power of 2
471 if (minalign > 2 * sizeof (void*))
472 asize += minalign;
473 LoftBuckets &pool = the_pool();
474 LoftPtr<void> uptr = pool.do_alloc (asize, minalign);
475 if (!uptr) return {};
476 LoftFree lfree = uptr.get_deleter();
477 assert_return (lfree.size >= asize, {});
478 char *const mem = (char*) uptr.release();
479 char *const alptr = align_forward (1 + mem, minalign);
480 assert_return (0 == (uintptr_t (alptr) & (minalign -1)), {});
481 assert_return (alptr >= mem + sizeof (BoundaryTag), {});
482 assert_return (mem + asize >= alptr + size, {});
483 BoundaryTag *bt = (BoundaryTag*) (alptr - sizeof (BoundaryTag));
484 bt->mem = mem;
485 bt->sz = lfree.size;
486 // printerr ("BoundaryTag: alloc: mem=%p sz=%-7u ptr=%p\n", bt->mem, bt->sz, alptr);
487 return alptr;
488}
489
491void
492Loft::AllocatorBase::loft_btfree (void *const p, const size_t size)
493{
494 if (!p) return;
495 if (ASE_UNLIKELY (no_allocators()))
496 return free (p);
497 assert_return (uintptr_t (p) > sizeof (BoundaryTag));
498 const size_t minalign = std::max (size_t (64), sizeof (BoundaryTag));
499 assert_return (0 == (uintptr_t (p) & (minalign -1)));
500 const char *const alptr = reinterpret_cast<char*> (p);
501 BoundaryTag *bt = (BoundaryTag*) (alptr - sizeof (BoundaryTag));
502 assert_return (bt->mem != nullptr);
503 assert_return (bt->mem <= (char*) bt);
504 assert_return (bt->sz >= sizeof (BoundaryTag));
505 assert_return (0 == (bt->sz & (minalign - 1)));
506 assert_return (alptr + size <= bt->mem + bt->sz);
507 // printerr ("BoundaryTag: mfree: mem=%p sz=%-7u ptr=%p\n", bt->mem, bt->sz, alptr);
508 LoftBuckets &pool = the_pool();
509 pool.do_free (bt->mem, bt->sz);
510}
511
512size_t
513loft_bucket_size (size_t nbytes)
514{
515 if (ASE_UNLIKELY (no_allocators()))
516 return nbytes;
517 const unsigned bindex = bucket_index (nbytes);
518 if (bindex >= NUMBER_OF_BUCKETS)
519 return 0; // size not supported
520 const size_t bsize = bucket_size (bindex);
521 return bsize;
522}
523
524void
525LoftFree::operator() (void *p)
526{
527 if (dtor_)
528 dtor_ (p);
529 // printerr ("--LOFT: %s: free: size=%d ptr=%p dtor_=%p\n", __func__, size, p, dtor_);
530 if (ASE_UNLIKELY (no_allocators()))
531 return free (p);
532
533 LoftBuckets &pool = the_pool();
534 pool.do_free (p, size);
535}
536
537void
538loft_set_notifier (const std::function<void()> &lowmem)
539{
540 assert_return (config_lowmem_cb == nullptr);
541 config_lowmem_cb = new std::function<void()> (lowmem);
542 // can be installed only once
543}
544
545void
546loft_set_growth_notifier (const std::function<void(size_t total,size_t needed)> &willgrow)
547{
548 assert_return (config_willgrow == nullptr);
549 config_willgrow = new std::function<void(size_t, size_t)> (willgrow);
550 // can be installed only once
551}
552
553void
554loft_set_config (const LoftConfig &config)
555{
556 // disable watermark to avoid spurious notifications during config updates
557 config_watermark = 0;
558 config_flags = config.flags;
559 config_preallocate = config.preallocate;
560 // reconfigure watermark
561 config_watermark = config.watermark;
562}
563
564void
565loft_get_config (LoftConfig &config)
566{
567 config.flags = config_flags;
568 config.preallocate = config_preallocate;
569 config.watermark = config_watermark;
570}
571
572size_t
573loft_grow_preallocate (size_t preallocation_amount)
574{
575 LoftBuckets &pool = the_pool();
576 BumpAllocator &bump_allocator = pool.bump_allocator;
577 const size_t totalmem = bump_allocator.totalmem();
578 // grow at least until config_preallocate
579 preallocation_amount = std::max (0 + config_preallocate, totalmem) - totalmem;
580 // grow at least to avoid watermark underrun
581 const size_t maxchunk = bump_allocator.free_block();
582 if (maxchunk <= config_watermark)
583 preallocation_amount = std::max (preallocation_amount, 0 + config_watermark);
584 // grow only if available memory is lower than requested
585 if (maxchunk < preallocation_amount)
586 {
587 config_lowmem_notified = 0;
588 // blocking call
589 const size_t allocated = bump_allocator.grow_spans (preallocation_amount, true);
590 config_preallocate = std::max (0 + config_preallocate, bump_allocator.totalmem());
591 return allocated;
592 }
593 return 0;
594}
595
596void
597loft_get_stats (LoftStats &stats)
598{
599 ArenaList arenas;
600 LoftBuckets &pool = the_pool();
601 BumpAllocator &bump_allocator = pool.bump_allocator;
602 bump_allocator.list_arenas (arenas);
603 stats.narenas = arenas.size();
604 stats.allocated = 0;
605 stats.available = 0;
606 stats.maxchunk = 0;
607 for (const auto &a : arenas)
608 {
609 stats.allocated += a.size;
610 stats.available += a.size - a.offset;
611 stats.maxchunk = std::max (stats.maxchunk, a.size - a.offset);
612 }
613 stats.buckets.clear();
614 for (size_t i = 0; i < NUMBER_OF_BUCKETS; i++)
615 {
616 size_t count = pool.count (i, arenas);
617 if (count)
618 stats.buckets.push_back (std::make_pair (bucket_size (i), count));
619 }
620 std::sort (stats.buckets.begin(), stats.buckets.end(), [] (auto &a, auto &b) {
621 return a.first < b.first;
622 });
623}
624
625String
626loft_stats_string (const LoftStats &stats)
627{
628 StringS s;
629 constexpr size_t MB = 1024 * 1024;
630 s.push_back (string_format ("%8u Arenas", stats.narenas));
631 s.push_back (string_format ("%8u MB allocated", stats.allocated / MB));
632 s.push_back (string_format ("%8u MB available", stats.available / MB));
633 s.push_back (string_format ("%8u KB maximum chunk", stats.maxchunk / 1024));
634 for (const auto &b : stats.buckets)
635 if (b.second && 0 == (b.first & 1023))
636 s.push_back (string_format ("%8u x %4u KB", b.second, b.first / 1024));
637 else if (b.second)
638 s.push_back (string_format ("%8u x %4u B", b.second, b.first));
639 size_t t = 0;
640 for (const auto &b : stats.buckets)
641 t += b.first * b.second;
642 s.push_back (string_format ("%8.1f MB in use", t * 1.0 / MB));
643 return string_join ("\n", s);
644}
645
646} // Ase
647
648
649// == tests ==
650namespace {
651using namespace Ase;
652
653static const size_t N_THREADS = std::thread::hardware_concurrency();
654
655TEST_INTEGRITY (loft_simple_tests);
656static void
657loft_simple_tests()
658{
659 using namespace Ase::Loft;
660 unsigned bi, bs;
661 bi = bucket_index (1); bs = bucket_size (bi); TCMP (bs, ==, 64u); TASSERT (bi == NUMBER_OF_POWER2_BUCKETS);
662 TASSERT (bi == bucket_index (0));
663 bi = bucket_index (63); bs = bucket_size (bi); TCMP (bs, ==, 64u); TASSERT (bi == NUMBER_OF_POWER2_BUCKETS);
664 bi = bucket_index (64); bs = bucket_size (bi); TCMP (bs, ==, 64u); TASSERT (bi == NUMBER_OF_POWER2_BUCKETS);
665 bi = bucket_index (65); bs = bucket_size (bi); TCMP (bs, ==, 128u); TASSERT (bi == NUMBER_OF_POWER2_BUCKETS + 1);
666 bi = bucket_index (127); bs = bucket_size (bi); TCMP (bs, ==, 128u); TASSERT (bi == NUMBER_OF_POWER2_BUCKETS + 1);
667 bi = bucket_index (128); bs = bucket_size (bi); TCMP (bs, ==, 128u); TASSERT (bi == NUMBER_OF_POWER2_BUCKETS + 1);
668 bi = bucket_index (129); bs = bucket_size (bi); TCMP (bs, ==, 192u); TASSERT (bi == NUMBER_OF_POWER2_BUCKETS + 2);
669 bi = bucket_index (SMALL_BLOCK_LIMIT - 1); bs = bucket_size (bi); TCMP (bs, ==, SMALL_BLOCK_LIMIT);
670 bi = bucket_index (SMALL_BLOCK_LIMIT); bs = bucket_size (bi); TCMP (bs, ==, SMALL_BLOCK_LIMIT);
671 TASSERT (bi + 1 == NUMBER_OF_BUCKETS);
672 bi = bucket_index (SMALL_BLOCK_LIMIT + 1); bs = bucket_size (bi); TCMP (bs, ==, SMALL_BLOCK_LIMIT * 2);
673 bi = bucket_index (SMALL_BLOCK_LIMIT * 2); bs = bucket_size (bi); TCMP (bs, ==, SMALL_BLOCK_LIMIT * 2);
674 bi = bucket_index (SMALL_BLOCK_LIMIT * 2 + 1); bs = bucket_size (bi); TCMP (bs, ==, SMALL_BLOCK_LIMIT * 2 * 2);
675 auto sp = loft_make_unique<String> ("Sample String");
676 TASSERT ("Sample String" == *sp);
677 struct Test2 {
678 bool *b_;
679 Test2 (bool *b) : b_ (b) {}
680 ~Test2() { *b_ = true; }
681 };
682 bool seen_loft_dtor = false;
683 LoftPtr<Test2> t2 = loft_make_unique<Test2> (&seen_loft_dtor);
684 TASSERT (t2.get() && seen_loft_dtor == false);
685 t2.reset();
686 TASSERT (!t2.get() && seen_loft_dtor == true);
687}
688
689inline constexpr size_t N_ALLOCS = 20000;
690inline constexpr size_t MAX_BLOCK_SIZE = 4 * 1024;
691
692static std::atomic<bool> thread_shuffle_done = false;
693
694struct ThreadData
695{
696 unsigned thread_id;
697 std::thread thread;
698 std::unique_ptr<std::mutex> to_free_mutex;
699 std::vector<LoftPtr<void>> to_free; // [N_THREADS * N_ALLOCS]
700 int n_to_free = 0;
701 int n_freed = 0;
702};
703static std::vector<ThreadData> tdata; // [N_THREADS]
704
705static void
706thread_shuffle_allocs (ThreadData *td)
707{
708 FastRng rng;
709 uint64_t lastval = -1;
710 for (size_t i = 0; i < N_ALLOCS; i++)
711 {
712 if (Test::verbose() && i % (N_ALLOCS / 20) == 0)
713 printout ("%c", '@' + td->thread_id);
714
715 // allocate block of random size and fill randomly
716 size_t bytes = rng.next() % MAX_BLOCK_SIZE + 8;
717 LoftPtr<void> mem = loft_alloc (bytes);
718 char *cmem = (char*) mem.get();
719 for (size_t f = 0; f < bytes; f++)
720 cmem[f] = rng.next();
721
722 // check that blocks are indeed randomly filled
723 TASSERT (lastval != *(uint64_t*) &cmem[bytes - 8]); // last entry should differ from last block
724 TASSERT (lastval != *(uint64_t*) &cmem[0]); // first entry is usually touched by allocator
725 lastval = *(uint64_t*) &cmem[bytes - 8];
726
727 // associate block with random thread
728 const size_t t = rng.next() % N_THREADS;
729 {
730 std::lock_guard<std::mutex> locker (*tdata[t].to_free_mutex);
731 tdata[t].to_free[tdata[t].n_to_free] = std::move (mem);
732 tdata[t].n_to_free++;
733 }
734
735 // shuffle thread execution order every once in a while
736 if (rng.next() % 97 == 0)
737 usleep (500); // concurrent shuffling for multi core
738
739 // free a block associated with this thread
740 LoftPtr<void> ptr;
741 std::lock_guard<std::mutex> locker (*td->to_free_mutex);
742 if (td->n_to_free > 0)
743 {
744 td->n_to_free--;
745 ptr = std::move (td->to_free[td->n_to_free]);
746 td->n_freed++;
747 }
748 // ptr is freed here
749 }
750 thread_shuffle_done = true;
751}
752
753TEST_INTEGRITY (loft_shuffle_thread_allocs);
754static void
755loft_shuffle_thread_allocs()
756{
757 thread_shuffle_done = false;
758 // allocate test data
759 tdata.resize (N_THREADS);
760 for (auto &td : tdata)
761 {
762 td.to_free_mutex = std::unique_ptr<std::mutex> (new std::mutex);
763 td.to_free.resize (N_THREADS * N_ALLOCS);
764 }
765 // start one thread per core
766 if (Test::verbose())
767 printout ("Starting %d threads for concurrent Loft usage...\n", N_THREADS);
768 for (size_t t = 0; t < N_THREADS; t++)
769 {
770 tdata[t].thread_id = 1 + t;
771 tdata[t].thread = std::thread (thread_shuffle_allocs, &tdata[t]);
772 }
773 // effectively assigns a timeout for main_loop->iterate()
774 main_loop->add ([] () { return !thread_shuffle_done; }, std::chrono::milliseconds (50));
775 // recurse into main loop for concurrent preallocations
776 while (!thread_shuffle_done) // wati until *any* thread is done
777 main_loop->iterate (true); // only allowed in unit tests
778 // when done, join threads
779 for (size_t t = 0; t < N_THREADS; t++)
780 tdata[t].thread.join();
781 // print stats
782 if (Test::verbose())
783 printout ("\n");
784 if (Test::verbose())
785 for (size_t t = 0; t < N_THREADS; t++)
786 printout ("Thread %c: %d blocks freed, %d blocks not freed\n",
787 '@' + tdata[t].thread_id, tdata[t].n_freed, tdata[t].n_to_free);
788 tdata.clear();
789
790 LoftStats lstats;
791 loft_get_stats (lstats);
792 if (Test::verbose())
793 printout ("Loft Stats:\n%s\n", loft_stats_string (lstats));
794}
795
796TEST_INTEGRITY (loft_allocator_tests);
797static void
798loft_allocator_tests()
799{
800 using Element = std::pair<double,uint64_t>;
801 {
803 v1.resize (2099);
805 for (auto sz : { 256, 512, 1024, 2048, 4*32768 })
806 v2.reserve (sz);
807 }
808}
809
810} // Anon
T aligned_alloc(T... args)
T begin(T... args)
Marsaglia multiply-with-carry generator, period ca 2^255.
Definition randomhash.hh:37
T clear(T... args)
T count(T... args)
#define ASE_UNLIKELY(expr)
Compiler hint to optimize for expr evaluating to false.
Definition cxxaux.hh:46
T end(T... args)
free
T get_deleter(T... args)
T get(T... args)
getenv
T hardware_concurrency(T... args)
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
Definition internal.hh:29
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
Definition internal.hh:73
#define assert_warn(expr)
Issue an assertion warning if expr evaluates to false.
Definition internal.hh:33
#define TEST_INTEGRITY(FUNC)
Register func as an integrity test.
Definition internal.hh:79
#define PAGESIZE
T make_pair(T... args)
T max(T... args)
munmap
Loft - A special purpose memory allocator for lock- and obstruction-free thread progress.
Definition loft.cc:17
Flags
Flags for allocator behavior.
Definition loft.hh:13
bool verbose()
Indicates whether tests should run verbosely.
Definition testing.cc:178
The Anklang C++ API namespace.
Definition api.hh:9
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
size_t maxchunk
Memory preallocated in Arenas.
Definition loft.hh:72
String string_join(const String &junctor, const StringS &strvec)
Join a number of strings.
Definition strings.cc:452
size_t available
Total number of arenas.
Definition loft.hh:70
size_t allocated
Memory still allocatable from Arenas.
Definition loft.hh:71
size_t preallocate
Amount of preallocated available memory.
Definition loft.hh:45
size_t watermark
Watermark to trigger async preallocation.
Definition loft.hh:46
Configuration for Loft allocations.
Definition loft.hh:44
Statistics for Loft allocations.
Definition loft.hh:67
T push_back(T... args)
T release(T... args)
T reset(T... args)
T size(T... args)
T sort(T... args)
typedef uintptr_t
strstr
A std::unique_ptr<> deleter for Loft allocations.
Definition loft.hh:20
static void loft_btfree(void *p, size_t size)
Free memory allocted from loft_btalloc().
Definition loft.cc:492
static void * loft_btalloc(size_t size, size_t align)
Alloc in a fashion similar to malloc(), using boundary tags.
Definition loft.cc:464
BumpAllocator - satisfy allocations by bumping an offset into mmap based spans.
Definition loft.cc:40
Lock-free, obstruction-free, non-coalescing, alloc-only bucket allocator.
Definition loft.cc:300
typedef size_t
sysconf
#define TASSERT(cond)
Unconditional test assertion, enters breakpoint if not fullfilled.
Definition testing.hh:24
#define TCMP(a, cmp, b)
Compare a and b according to operator cmp, verbose on failiure.
Definition testing.hh:23
wmemset