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