12#define MDEBUG(...) Ase::debug ("memory", __VA_ARGS__)
13#define VDEBUG(...) Ase::debug ("memory", __VA_ARGS__)
15#define MEM_ALIGN(addr, ALIGNMENT) (ALIGNMENT * size_t ((size_t (addr) + ALIGNMENT - 1) / ALIGNMENT))
20static constexpr size_t MINIMUM_HUGEPAGE = 2 * 1024 * 1024;
41 void* bump_alloc (
size_t size);
42 size_t grow_spans (
size_t needed,
bool preallocating);
43 size_t free_block ()
const;
45 size_t totalmem ()
const {
return totalmem_; }
47 char* memmap (
size_t size,
bool growsup);
48 char* remap (
void *addr,
size_t oldsize,
size_t size);
50 char *mmstart =
nullptr;
51 boost::atomic<uintptr_t> offset = 0;
52 boost::atomic<uintptr_t> mmsize = 0;
53 boost::atomic<MMSpan*> next =
nullptr;
55 boost::atomic<size_t> totalmem_ = 0;
56 boost::atomic<MMSpan*> spans_ =
nullptr;
61BumpAllocator::list_arenas (
ArenaList &arenas)
63 for (MMSpan *lspan = spans_; lspan; lspan = lspan->next)
64 arenas.
push_back ({ uintptr_t (lspan->mmstart), lspan->offset, lspan->mmsize });
68BumpAllocator::free_block ()
const
71 MMSpan *lspan = spans_;
72 while (lspan && lspan->next)
76 return lspan->mmsize - lspan->offset;
80BumpAllocator::grow_spans (
size_t needed,
bool preallocating)
82 const size_t entry_total = totalmem_;
84 if (entry_total < totalmem_)
89 MMSpan *lspan = spans_;
90 while (lspan && lspan->next)
92 totalmem += lspan->mmsize;
97 const size_t oldsize = lspan->mmsize;
98 size_t newsize = oldsize * 2;
99 while (newsize < needed)
101 char *mmstart = remap (lspan->mmstart, oldsize, newsize);
102 if (mmstart == lspan->mmstart)
104 if (config_flags & PREFAULT_PAGES)
105 for (
size_t i = oldsize; i < newsize; i +=
PAGESIZE)
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;
114 size_t mmsize = MINIMUM_HUGEPAGE;
115 while (mmsize < needed)
117 mmsize = MEM_ALIGN (mmsize, MINIMUM_HUGEPAGE);
118 char *mmstart = memmap (mmsize, !lspan);
121 VDEBUG (
"%s:%u: false\n", __func__, __LINE__);
124 for (
size_t i = 0; i < mmsize; i +=
PAGESIZE)
126 MMSpan *nspan =
new (mmstart) MMSpan();
127 nspan->offset = MEM_ALIGN (
sizeof (MMSpan), 64);
128 nspan->mmstart = mmstart;
129 nspan->mmsize = mmsize;
134 totalmem_ = totalmem + nspan->mmsize;
135 VDEBUG (
"%s:%u: new-total=%dM: true\n", __func__, __LINE__, totalmem_ / (1024 * 1024));
140BumpAllocator::remap (
void *addr,
size_t oldsize,
size_t size)
142 assert_return (MEM_ALIGN (size, MINIMUM_HUGEPAGE) == size,
nullptr);
145 char *memory = (
char*) mremap (addr, oldsize, size, 0, addr);
146 if (memory == MAP_FAILED)
148 VDEBUG (
"%s:%u: mremap: %p %d %d: %s\n", __func__, __LINE__, addr, oldsize, size, strerror (errno));
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));
158BumpAllocator::memmap (
size_t size,
bool growsup)
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;
164 const bool anon_hugepages =
true;
165 const bool reserved_hugepages =
false;
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)
174 VDEBUG (
"%s:%d: addr-hint size=%uMB: %p\n", __FILE__, __LINE__, giga / (1024 * 1024), addr);
178 if (addr == MAP_FAILED)
183 if (reserved_hugepages)
185 memory = (
char*) mmap (addr, size, PROTECTION, PRIVANON | MAP_HUGETLB, -1, 0);
186 if (memory != MAP_FAILED)
188 VDEBUG (
"%s:%u: mmap: %p %d HUGETLB: %p\n", __func__, __LINE__, addr, size, memory);
191 VDEBUG (
"%s:%u: mmap: %p %d HUGETLB: %s\n", __func__, __LINE__, addr, size, strerror (errno));
195 size_t areasize =
size + MINIMUM_HUGEPAGE;
196 memory = (
char*) mmap (addr, areasize, PROTECTION, PRIVANON, -1, 0);
197 if (memory == MAP_FAILED)
199 VDEBUG (
"%s:%u: mmap: %p %d: %s\n", __func__, __LINE__, addr, areasize, strerror (errno));
202 VDEBUG (
"%s:%u: mmap: %p %d: %p\n", __func__, __LINE__, addr, areasize, 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));
211 extra = areasize -
size;
213 if (extra && munmap (memory + areasize, extra) != 0)
214 VDEBUG (
"%s: munmap(%p,%u) failed: %s\n", __func__, memory + areasize, extra, strerror (errno));
216 VDEBUG (
"%s:%u: mmap page-align: size=%d at: %p\n", __func__, __LINE__, size, memory);
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));
225BumpAllocator::bump_alloc (
size_t size)
228 for (MMSpan *span = spans_; span; span = span->next)
233 nmark = omark +
size;
234 if (nmark > span->mmsize)
237 while (!span->offset.compare_exchange_weak (omark, nmark));
240 span->mmsize - span->offset < config_watermark)
243 if (0 == config_lowmem_notified++)
250 return span->mmstart + omark;
254 const size_t old_totalmem_ = totalmem_;
255 const size_t allocated = grow_spans (size,
false);
256 if (allocated && config_willgrow) {
257 std::function<void(
size_t,
size_t)> *willgrow = config_willgrow;
258 (*willgrow) (old_totalmem_,
size);
261 return bump_alloc (size);
265static constexpr unsigned SMALL_BLOCK_LIMIT = 8192;
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;
272bucket_size (
unsigned index)
274 if (index < NUMBER_OF_POWER2_BUCKETS)
276 return (index - NUMBER_OF_POWER2_BUCKETS + 1) * 64;
280static inline unsigned
281bucket_index (
unsigned long long n)
284 if (n <= SMALL_BLOCK_LIMIT)
285 return NUMBER_OF_POWER2_BUCKETS + (n - 1) / 64;
286 constexpr unsigned CLZLL_BITS =
sizeof (0ull) * 8;
287 const unsigned upper_power2_shift = CLZLL_BITS - __builtin_clzll (n - 1);
288 return upper_power2_shift;
303 void do_free (
void *mem,
size_t size);
304 size_t count (
unsigned bucket_index,
const ArenaList &arenas);
308 boost::atomic<Block*> intr_ptr_ =
nullptr;
310 static_assert (
sizeof (Block) <= 64);
313 CANARY0 = 0xbe4da62f087c3519ull
315 bool maybeok (
const Block *block,
const ArenaList &arenas);
321LoftBuckets::do_alloc (
size_t size,
size_t align)
326 const unsigned bindex = bucket_index (size);
327 if (bindex >= NUMBER_OF_BUCKETS)
329 const size_t bsize = bucket_size (bindex);
330 auto block = buckets_[bindex].pop();
334 block = (Block*) bump_allocator.bump_alloc (bsize);
341LoftBuckets::do_free (
void *mem,
size_t size)
344 const unsigned bindex = bucket_index (size);
346 const size_t bsize = bucket_size (bindex);
348 auto block =
new (mem) Block();
349 buckets_[bindex].push (block);
353LoftBuckets::maybeok (
const Block *block,
const ArenaList &arenas)
360 bool contained =
false;
361 for (
const auto &a : arenas)
362 if (addr >= a.addr && addr < a.addr + a.
size)
369 block->canary0 == CANARY0)
375LoftBuckets::count (
unsigned bucket_index,
const ArenaList &arenas)
380 for (Block *block = buckets_[bucket_index].peek(); block; block = block->intr_ptr_.load())
381 if (maybeok (block, arenas))
400 const char *f =
getenv (
"ASE_DEBUG");
401 no_allocators_ = f && (
strstr (f,
":no-allocators") ||
strstr (f,
"no-allocators") == f);
403 return no_allocators_;
418loft_alloc (
size_t size,
size_t align)
429loft_calloc (
size_t nelem,
size_t elemsize,
size_t align)
431 const bool overflows = elemsize > 1 && nelem > ~size_t (0) / elemsize;
434 const size_t size = nelem * elemsize;
439 wmemset ((
wchar_t*) p, 0, size /
sizeof (
wchar_t));
440 return { p, {
size } };
445 wmemset ((
wchar_t*) ptr.
get(), 0, size / sizeof (
wchar_t));
450align_forward (
const char *
const ptr,
const size_t alignment)
noexcept
454 return reinterpret_cast<char*
> (alptr);
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)));
471 if (minalign > 2 *
sizeof (
void*))
475 if (!uptr)
return {};
478 char *
const mem = (
char*) uptr.
release();
479 char *
const alptr = align_forward (1 + mem, minalign);
483 BoundaryTag *bt = (BoundaryTag*) (alptr -
sizeof (BoundaryTag));
498 const size_t minalign =
std::max (
size_t (64),
sizeof (BoundaryTag));
500 const char *
const alptr =
reinterpret_cast<char*
> (p);
501 BoundaryTag *bt = (BoundaryTag*) (alptr -
sizeof (BoundaryTag));
509 pool.do_free (bt->mem, bt->sz);
513loft_bucket_size (
size_t nbytes)
517 const unsigned bindex = bucket_index (nbytes);
518 if (bindex >= NUMBER_OF_BUCKETS)
520 const size_t bsize = bucket_size (bindex);
525LoftFree::operator() (
void *p)
534 pool.do_free (p, size);
546loft_set_growth_notifier (
const std::function<
void(
size_t total,
size_t needed)> &willgrow)
557 config_watermark = 0;
558 config_flags = config.flags;
567 config.flags = config_flags;
573loft_grow_preallocate (
size_t preallocation_amount)
577 const size_t totalmem = bump_allocator.totalmem();
579 preallocation_amount =
std::max (0 + config_preallocate, totalmem) - totalmem;
581 const size_t maxchunk = bump_allocator.free_block();
582 if (maxchunk <= config_watermark)
583 preallocation_amount =
std::max (preallocation_amount, 0 + config_watermark);
585 if (maxchunk < preallocation_amount)
588 const size_t allocated = bump_allocator.grow_spans (preallocation_amount,
true);
589 config_preallocate =
std::max (0 + config_preallocate, bump_allocator.totalmem());
591 config_lowmem_notified = 0;
603 bump_allocator.list_arenas (arenas);
604 stats.narenas = arenas.
size();
608 for (
const auto &a : arenas)
614 stats.buckets.
clear();
615 for (
size_t i = 0; i < NUMBER_OF_BUCKETS; i++)
617 size_t count = pool.count (i, arenas);
622 return a.first < b.first;
627loft_stats_string (
const LoftStats &stats)
630 constexpr size_t MB = 1024 * 1024;
635 for (
const auto &b : stats.buckets)
636 if (b.second && 0 == (b.first & 1023))
637 s.push_back (
string_format (
"%8u x %4u KB", b.second, b.first / 1024));
641 for (
const auto &b : stats.buckets)
642 t += b.first * b.second;
662 bi = bucket_index (1); bs = bucket_size (bi);
TCMP (bs, ==, 64u);
TASSERT (bi == NUMBER_OF_POWER2_BUCKETS);
663 TASSERT (bi == bucket_index (0));
664 bi = bucket_index (63); bs = bucket_size (bi);
TCMP (bs, ==, 64u);
TASSERT (bi == NUMBER_OF_POWER2_BUCKETS);
665 bi = bucket_index (64); bs = bucket_size (bi);
TCMP (bs, ==, 64u);
TASSERT (bi == NUMBER_OF_POWER2_BUCKETS);
666 bi = bucket_index (65); bs = bucket_size (bi);
TCMP (bs, ==, 128u);
TASSERT (bi == NUMBER_OF_POWER2_BUCKETS + 1);
667 bi = bucket_index (127); bs = bucket_size (bi);
TCMP (bs, ==, 128u);
TASSERT (bi == NUMBER_OF_POWER2_BUCKETS + 1);
668 bi = bucket_index (128); bs = bucket_size (bi);
TCMP (bs, ==, 128u);
TASSERT (bi == NUMBER_OF_POWER2_BUCKETS + 1);
669 bi = bucket_index (129); bs = bucket_size (bi);
TCMP (bs, ==, 192u);
TASSERT (bi == NUMBER_OF_POWER2_BUCKETS + 2);
670 bi = bucket_index (SMALL_BLOCK_LIMIT - 1); bs = bucket_size (bi);
TCMP (bs, ==, SMALL_BLOCK_LIMIT);
671 bi = bucket_index (SMALL_BLOCK_LIMIT); bs = bucket_size (bi);
TCMP (bs, ==, SMALL_BLOCK_LIMIT);
672 TASSERT (bi + 1 == NUMBER_OF_BUCKETS);
673 bi = bucket_index (SMALL_BLOCK_LIMIT + 1); bs = bucket_size (bi);
TCMP (bs, ==, SMALL_BLOCK_LIMIT * 2);
674 bi = bucket_index (SMALL_BLOCK_LIMIT * 2); bs = bucket_size (bi);
TCMP (bs, ==, SMALL_BLOCK_LIMIT * 2);
675 bi = bucket_index (SMALL_BLOCK_LIMIT * 2 + 1); bs = bucket_size (bi);
TCMP (bs, ==, SMALL_BLOCK_LIMIT * 2 * 2);
676 auto sp = loft_make_unique<String> (
"Sample String");
677 TASSERT (
"Sample String" == *sp);
680 Test2 (
bool *b) : b_ (b) {}
681 ~Test2() { *b_ =
true; }
683 bool seen_loft_dtor =
false;
685 TASSERT (t2.
get() && seen_loft_dtor ==
false);
687 TASSERT (!t2.
get() && seen_loft_dtor ==
true);
690inline constexpr size_t N_ALLOCS = 20000;
691inline constexpr size_t MAX_BLOCK_SIZE = 4 * 1024;
698 size_t n_allocs = N_ALLOCS;
708thread_shuffle_allocs (ThreadData *td)
712 for (
size_t i = 0; i < td->n_allocs; i++)
715 printout (
"%c",
'@' + td->thread_id);
718 size_t bytes = rng.next() % MAX_BLOCK_SIZE + 8;
720 char *cmem = (
char*) mem.
get();
721 for (
size_t f = 0; f < bytes; f++)
722 cmem[f] = rng.next();
725 TASSERT (lastval != *(uint64_t*) &cmem[bytes - 8]);
726 TASSERT (lastval != *(uint64_t*) &cmem[0]);
727 lastval = *(
uint64_t*) &cmem[bytes - 8];
730 const size_t t = rng.next() % N_THREADS;
733 tdata[t].to_free[tdata[t].n_to_free] = std::move (mem);
734 tdata[t].n_to_free++;
738 if (rng.next() % 97 == 0)
744 if (td->n_to_free > 0)
747 ptr = std::move (td->to_free[td->n_to_free]);
752 thread_shuffle_pending--;
757run_thread_shuffle_allocs (
bool varying_workloads)
759 thread_shuffle_pending = N_THREADS;
761 tdata.resize (N_THREADS);
762 for (
auto &td : tdata)
765 td.to_free.resize (N_THREADS * N_ALLOCS);
769 printout (
"Starting %d threads for concurrent Loft usage...\n", N_THREADS);
770 for (
size_t t = 0; t < N_THREADS; t++)
772 tdata[t].thread_id = 1 + t;
773 if (varying_workloads && t < N_THREADS / 2)
774 tdata[t].n_allocs = 1;
775 tdata[t].thread =
std::thread (thread_shuffle_allocs, &tdata[t]);
778 while (thread_shuffle_pending)
779 main_loop->iterate (
true);
781 for (
size_t t = 0; t < N_THREADS; t++)
782 tdata[t].thread.join();
787 for (
size_t t = 0; t < N_THREADS; t++)
788 printout (
"Thread %c: %d blocks freed, %d blocks not freed\n",
789 '@' + tdata[t].thread_id, tdata[t].n_freed, tdata[t].n_to_free);
793 loft_get_stats (lstats);
795 printout (
"Loft Stats:\n%s\n", loft_stats_string (lstats));
800loft_shuffle_thread_allocs()
802 run_thread_shuffle_allocs (
false);
807loft_shuffle_uneven_thread_allocs()
810 run_thread_shuffle_allocs (
true);
815loft_allocator_tests()
822 for (
auto sz : { 256, 512, 1024, 2048, 4*32768 })
T aligned_alloc(T... args)
Marsaglia multiply-with-carry generator, period ca 2^255.
#define ASE_UNLIKELY(expr)
Compiler hint to optimize for expr evaluating to false.
T hardware_concurrency(T... args)
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
#define assert_warn(expr)
Issue an assertion warning if expr evaluates to false.
#define TEST_INTEGRITY(FUNC)
Register func as an integrity test.
Loft - A special purpose memory allocator for lock- and obstruction-free thread progress.
Flags
Flags for allocator behavior.
bool verbose()
Indicates whether tests should run verbosely.
The Anklang C++ API namespace.
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.
String string_join(const String &junctor, const StringS &strvec)
Join a number of strings.
size_t available
Total number of arenas.
size_t allocated
Memory still allocatable from Arenas.
void main_loop_wakeup()
Wake up the event loop.
size_t preallocate
Amount of preallocated available memory.
size_t watermark
Watermark to trigger async preallocation.
Configuration for Loft allocations.
Statistics for Loft allocations.
A std::unique_ptr<> deleter for Loft allocations.
static void loft_btfree(void *p, size_t size)
Free memory allocted from loft_btalloc().
static void * loft_btalloc(size_t size, size_t align)
Alloc in a fashion similar to malloc(), using boundary tags.
BumpAllocator - satisfy allocations by bumping an offset into mmap based spans.
Lock-free, obstruction-free, non-coalescing, alloc-only bucket allocator.
#define TASSERT(cond)
Unconditional test assertion, enters breakpoint if not fullfilled.
#define TCMP(a, cmp, b)
Compare a and b according to operator cmp, verbose on failiure.