Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/system/lib/mimalloc/src/arena.c
6178 views
1
/* ----------------------------------------------------------------------------
2
Copyright (c) 2019-2023, Microsoft Research, Daan Leijen
3
This is free software; you can redistribute it and/or modify it under the
4
terms of the MIT license. A copy of the license can be found in the file
5
"LICENSE" at the root of this distribution.
6
-----------------------------------------------------------------------------*/
7
8
/* ----------------------------------------------------------------------------
9
"Arenas" are fixed area's of OS memory from which we can allocate
10
large blocks (>= MI_ARENA_MIN_BLOCK_SIZE, 4MiB).
11
In contrast to the rest of mimalloc, the arenas are shared between
12
threads and need to be accessed using atomic operations.
13
14
Arenas are used to for huge OS page (1GiB) reservations or for reserving
15
OS memory upfront which can be improve performance or is sometimes needed
16
on embedded devices. We can also employ this with WASI or `sbrk` systems
17
to reserve large arenas upfront and be able to reuse the memory more effectively.
18
19
The arena allocation needs to be thread safe and we use an atomic bitmap to allocate.
20
-----------------------------------------------------------------------------*/
21
#include "mimalloc.h"
22
#include "mimalloc/internal.h"
23
#include "mimalloc/atomic.h"
24
25
#include <string.h> // memset
26
#include <errno.h> // ENOMEM
27
28
#include "bitmap.h" // atomic bitmap
29
30
/* -----------------------------------------------------------
31
Arena allocation
32
----------------------------------------------------------- */
33
34
// Block info: bit 0 contains the `in_use` bit, the upper bits the
35
// size in count of arena blocks.
36
typedef uintptr_t mi_block_info_t;
37
#define MI_ARENA_BLOCK_SIZE (MI_SEGMENT_SIZE) // 64MiB (must be at least MI_SEGMENT_ALIGN)
38
#define MI_ARENA_MIN_OBJ_SIZE (MI_ARENA_BLOCK_SIZE/2) // 32MiB
39
#define MI_MAX_ARENAS (112) // not more than 126 (since we use 7 bits in the memid and an arena index + 1)
40
41
// A memory arena descriptor
42
typedef struct mi_arena_s {
43
mi_arena_id_t id; // arena id; 0 for non-specific
44
mi_memid_t memid; // memid of the memory area
45
_Atomic(uint8_t*) start; // the start of the memory area
46
size_t block_count; // size of the area in arena blocks (of `MI_ARENA_BLOCK_SIZE`)
47
size_t field_count; // number of bitmap fields (where `field_count * MI_BITMAP_FIELD_BITS >= block_count`)
48
size_t meta_size; // size of the arena structure itself (including its bitmaps)
49
mi_memid_t meta_memid; // memid of the arena structure itself (OS or static allocation)
50
int numa_node; // associated NUMA node
51
bool exclusive; // only allow allocations if specifically for this arena
52
bool is_large; // memory area consists of large- or huge OS pages (always committed)
53
_Atomic(size_t) search_idx; // optimization to start the search for free blocks
54
_Atomic(mi_msecs_t) purge_expire; // expiration time when blocks should be decommitted from `blocks_decommit`.
55
mi_bitmap_field_t* blocks_dirty; // are the blocks potentially non-zero?
56
mi_bitmap_field_t* blocks_committed; // are the blocks committed? (can be NULL for memory that cannot be decommitted)
57
mi_bitmap_field_t* blocks_purge; // blocks that can be (reset) decommitted. (can be NULL for memory that cannot be (reset) decommitted)
58
mi_bitmap_field_t* blocks_abandoned; // blocks that start with an abandoned segment. (This crosses API's but it is convenient to have here)
59
mi_bitmap_field_t blocks_inuse[1]; // in-place bitmap of in-use blocks (of size `field_count`)
60
// do not add further fields here as the dirty, committed, purged, and abandoned bitmaps follow the inuse bitmap fields.
61
} mi_arena_t;
62
63
64
// The available arenas
65
static mi_decl_cache_align _Atomic(mi_arena_t*) mi_arenas[MI_MAX_ARENAS];
66
static mi_decl_cache_align _Atomic(size_t) mi_arena_count; // = 0
67
68
69
//static bool mi_manage_os_memory_ex2(void* start, size_t size, bool is_large, int numa_node, bool exclusive, mi_memid_t memid, mi_arena_id_t* arena_id) mi_attr_noexcept;
70
71
/* -----------------------------------------------------------
72
Arena id's
73
id = arena_index + 1
74
----------------------------------------------------------- */
75
76
static size_t mi_arena_id_index(mi_arena_id_t id) {
77
return (size_t)(id <= 0 ? MI_MAX_ARENAS : id - 1);
78
}
79
80
static mi_arena_id_t mi_arena_id_create(size_t arena_index) {
81
mi_assert_internal(arena_index < MI_MAX_ARENAS);
82
return (int)arena_index + 1;
83
}
84
85
mi_arena_id_t _mi_arena_id_none(void) {
86
return 0;
87
}
88
89
static bool mi_arena_id_is_suitable(mi_arena_id_t arena_id, bool arena_is_exclusive, mi_arena_id_t req_arena_id) {
90
return ((!arena_is_exclusive && req_arena_id == _mi_arena_id_none()) ||
91
(arena_id == req_arena_id));
92
}
93
94
bool _mi_arena_memid_is_suitable(mi_memid_t memid, mi_arena_id_t request_arena_id) {
95
if (memid.memkind == MI_MEM_ARENA) {
96
return mi_arena_id_is_suitable(memid.mem.arena.id, memid.mem.arena.is_exclusive, request_arena_id);
97
}
98
else {
99
return mi_arena_id_is_suitable(_mi_arena_id_none(), false, request_arena_id);
100
}
101
}
102
103
bool _mi_arena_memid_is_os_allocated(mi_memid_t memid) {
104
return (memid.memkind == MI_MEM_OS);
105
}
106
107
/* -----------------------------------------------------------
108
Arena allocations get a (currently) 16-bit memory id where the
109
lower 8 bits are the arena id, and the upper bits the block index.
110
----------------------------------------------------------- */
111
112
static size_t mi_block_count_of_size(size_t size) {
113
return _mi_divide_up(size, MI_ARENA_BLOCK_SIZE);
114
}
115
116
static size_t mi_arena_block_size(size_t bcount) {
117
return (bcount * MI_ARENA_BLOCK_SIZE);
118
}
119
120
static size_t mi_arena_size(mi_arena_t* arena) {
121
return mi_arena_block_size(arena->block_count);
122
}
123
124
static mi_memid_t mi_memid_create_arena(mi_arena_id_t id, bool is_exclusive, mi_bitmap_index_t bitmap_index) {
125
mi_memid_t memid = _mi_memid_create(MI_MEM_ARENA);
126
memid.mem.arena.id = id;
127
memid.mem.arena.block_index = bitmap_index;
128
memid.mem.arena.is_exclusive = is_exclusive;
129
return memid;
130
}
131
132
static bool mi_arena_memid_indices(mi_memid_t memid, size_t* arena_index, mi_bitmap_index_t* bitmap_index) {
133
mi_assert_internal(memid.memkind == MI_MEM_ARENA);
134
*arena_index = mi_arena_id_index(memid.mem.arena.id);
135
*bitmap_index = memid.mem.arena.block_index;
136
return memid.mem.arena.is_exclusive;
137
}
138
139
140
141
/* -----------------------------------------------------------
142
Special static area for mimalloc internal structures
143
to avoid OS calls (for example, for the arena metadata)
144
----------------------------------------------------------- */
145
146
#define MI_ARENA_STATIC_MAX (MI_INTPTR_SIZE*MI_KiB) // 8 KiB on 64-bit
147
148
static mi_decl_cache_align uint8_t mi_arena_static[MI_ARENA_STATIC_MAX]; // must be cache aligned, see issue #895
149
static mi_decl_cache_align _Atomic(size_t) mi_arena_static_top;
150
151
static void* mi_arena_static_zalloc(size_t size, size_t alignment, mi_memid_t* memid) {
152
*memid = _mi_memid_none();
153
if (size == 0 || size > MI_ARENA_STATIC_MAX) return NULL;
154
const size_t toplow = mi_atomic_load_relaxed(&mi_arena_static_top);
155
if ((toplow + size) > MI_ARENA_STATIC_MAX) return NULL;
156
157
// try to claim space
158
if (alignment < MI_MAX_ALIGN_SIZE) { alignment = MI_MAX_ALIGN_SIZE; }
159
const size_t oversize = size + alignment - 1;
160
if (toplow + oversize > MI_ARENA_STATIC_MAX) return NULL;
161
const size_t oldtop = mi_atomic_add_acq_rel(&mi_arena_static_top, oversize);
162
size_t top = oldtop + oversize;
163
if (top > MI_ARENA_STATIC_MAX) {
164
// try to roll back, ok if this fails
165
mi_atomic_cas_strong_acq_rel(&mi_arena_static_top, &top, oldtop);
166
return NULL;
167
}
168
169
// success
170
*memid = _mi_memid_create(MI_MEM_STATIC);
171
memid->initially_zero = true;
172
const size_t start = _mi_align_up(oldtop, alignment);
173
uint8_t* const p = &mi_arena_static[start];
174
_mi_memzero_aligned(p, size);
175
return p;
176
}
177
178
static void* mi_arena_meta_zalloc(size_t size, mi_memid_t* memid, mi_stats_t* stats) {
179
*memid = _mi_memid_none();
180
181
// try static
182
void* p = mi_arena_static_zalloc(size, MI_MAX_ALIGN_SIZE, memid);
183
if (p != NULL) return p;
184
185
// or fall back to the OS
186
p = _mi_os_alloc(size, memid, stats);
187
if (p == NULL) return NULL;
188
189
// zero the OS memory if needed
190
if (!memid->initially_zero) {
191
_mi_memzero_aligned(p, size);
192
memid->initially_zero = true;
193
}
194
return p;
195
}
196
197
static void mi_arena_meta_free(void* p, mi_memid_t memid, size_t size, mi_stats_t* stats) {
198
if (mi_memkind_is_os(memid.memkind)) {
199
_mi_os_free(p, size, memid, stats);
200
}
201
else {
202
mi_assert(memid.memkind == MI_MEM_STATIC);
203
}
204
}
205
206
static void* mi_arena_block_start(mi_arena_t* arena, mi_bitmap_index_t bindex) {
207
return (arena->start + mi_arena_block_size(mi_bitmap_index_bit(bindex)));
208
}
209
210
211
/* -----------------------------------------------------------
212
Thread safe allocation in an arena
213
----------------------------------------------------------- */
214
215
// claim the `blocks_inuse` bits
216
static bool mi_arena_try_claim(mi_arena_t* arena, size_t blocks, mi_bitmap_index_t* bitmap_idx, mi_stats_t* stats)
217
{
218
size_t idx = 0; // mi_atomic_load_relaxed(&arena->search_idx); // start from last search; ok to be relaxed as the exact start does not matter
219
if (_mi_bitmap_try_find_from_claim_across(arena->blocks_inuse, arena->field_count, idx, blocks, bitmap_idx, stats)) {
220
mi_atomic_store_relaxed(&arena->search_idx, mi_bitmap_index_field(*bitmap_idx)); // start search from found location next time around
221
return true;
222
};
223
return false;
224
}
225
226
227
/* -----------------------------------------------------------
228
Arena Allocation
229
----------------------------------------------------------- */
230
231
static mi_decl_noinline void* mi_arena_try_alloc_at(mi_arena_t* arena, size_t arena_index, size_t needed_bcount,
232
bool commit, mi_memid_t* memid, mi_os_tld_t* tld)
233
{
234
MI_UNUSED(arena_index);
235
mi_assert_internal(mi_arena_id_index(arena->id) == arena_index);
236
237
mi_bitmap_index_t bitmap_index;
238
if (!mi_arena_try_claim(arena, needed_bcount, &bitmap_index, tld->stats)) return NULL;
239
240
// claimed it!
241
void* p = mi_arena_block_start(arena, bitmap_index);
242
*memid = mi_memid_create_arena(arena->id, arena->exclusive, bitmap_index);
243
memid->is_pinned = arena->memid.is_pinned;
244
245
// none of the claimed blocks should be scheduled for a decommit
246
if (arena->blocks_purge != NULL) {
247
// this is thread safe as a potential purge only decommits parts that are not yet claimed as used (in `blocks_inuse`).
248
_mi_bitmap_unclaim_across(arena->blocks_purge, arena->field_count, needed_bcount, bitmap_index);
249
}
250
251
// set the dirty bits (todo: no need for an atomic op here?)
252
if (arena->memid.initially_zero && arena->blocks_dirty != NULL) {
253
memid->initially_zero = _mi_bitmap_claim_across(arena->blocks_dirty, arena->field_count, needed_bcount, bitmap_index, NULL);
254
}
255
256
// set commit state
257
if (arena->blocks_committed == NULL) {
258
// always committed
259
memid->initially_committed = true;
260
}
261
else if (commit) {
262
// commit requested, but the range may not be committed as a whole: ensure it is committed now
263
memid->initially_committed = true;
264
bool any_uncommitted;
265
_mi_bitmap_claim_across(arena->blocks_committed, arena->field_count, needed_bcount, bitmap_index, &any_uncommitted);
266
if (any_uncommitted) {
267
bool commit_zero = false;
268
if (!_mi_os_commit(p, mi_arena_block_size(needed_bcount), &commit_zero, tld->stats)) {
269
memid->initially_committed = false;
270
}
271
else {
272
if (commit_zero) { memid->initially_zero = true; }
273
}
274
}
275
}
276
else {
277
// no need to commit, but check if already fully committed
278
memid->initially_committed = _mi_bitmap_is_claimed_across(arena->blocks_committed, arena->field_count, needed_bcount, bitmap_index);
279
}
280
281
return p;
282
}
283
284
// allocate in a speficic arena
285
static void* mi_arena_try_alloc_at_id(mi_arena_id_t arena_id, bool match_numa_node, int numa_node, size_t size, size_t alignment,
286
bool commit, bool allow_large, mi_arena_id_t req_arena_id, mi_memid_t* memid, mi_os_tld_t* tld )
287
{
288
MI_UNUSED_RELEASE(alignment);
289
mi_assert_internal(alignment <= MI_SEGMENT_ALIGN);
290
const size_t bcount = mi_block_count_of_size(size);
291
const size_t arena_index = mi_arena_id_index(arena_id);
292
mi_assert_internal(arena_index < mi_atomic_load_relaxed(&mi_arena_count));
293
mi_assert_internal(size <= mi_arena_block_size(bcount));
294
295
// Check arena suitability
296
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[arena_index]);
297
if (arena == NULL) return NULL;
298
if (!allow_large && arena->is_large) return NULL;
299
if (!mi_arena_id_is_suitable(arena->id, arena->exclusive, req_arena_id)) return NULL;
300
if (req_arena_id == _mi_arena_id_none()) { // in not specific, check numa affinity
301
const bool numa_suitable = (numa_node < 0 || arena->numa_node < 0 || arena->numa_node == numa_node);
302
if (match_numa_node) { if (!numa_suitable) return NULL; }
303
else { if (numa_suitable) return NULL; }
304
}
305
306
// try to allocate
307
void* p = mi_arena_try_alloc_at(arena, arena_index, bcount, commit, memid, tld);
308
mi_assert_internal(p == NULL || _mi_is_aligned(p, alignment));
309
return p;
310
}
311
312
313
// allocate from an arena with fallback to the OS
314
static mi_decl_noinline void* mi_arena_try_alloc(int numa_node, size_t size, size_t alignment,
315
bool commit, bool allow_large,
316
mi_arena_id_t req_arena_id, mi_memid_t* memid, mi_os_tld_t* tld )
317
{
318
MI_UNUSED(alignment);
319
mi_assert_internal(alignment <= MI_SEGMENT_ALIGN);
320
const size_t max_arena = mi_atomic_load_relaxed(&mi_arena_count);
321
if mi_likely(max_arena == 0) return NULL;
322
323
if (req_arena_id != _mi_arena_id_none()) {
324
// try a specific arena if requested
325
if (mi_arena_id_index(req_arena_id) < max_arena) {
326
void* p = mi_arena_try_alloc_at_id(req_arena_id, true, numa_node, size, alignment, commit, allow_large, req_arena_id, memid, tld);
327
if (p != NULL) return p;
328
}
329
}
330
else {
331
// try numa affine allocation
332
for (size_t i = 0; i < max_arena; i++) {
333
void* p = mi_arena_try_alloc_at_id(mi_arena_id_create(i), true, numa_node, size, alignment, commit, allow_large, req_arena_id, memid, tld);
334
if (p != NULL) return p;
335
}
336
337
// try from another numa node instead..
338
if (numa_node >= 0) { // if numa_node was < 0 (no specific affinity requested), all arena's have been tried already
339
for (size_t i = 0; i < max_arena; i++) {
340
void* p = mi_arena_try_alloc_at_id(mi_arena_id_create(i), false /* only proceed if not numa local */, numa_node, size, alignment, commit, allow_large, req_arena_id, memid, tld);
341
if (p != NULL) return p;
342
}
343
}
344
}
345
return NULL;
346
}
347
348
// try to reserve a fresh arena space
349
static bool mi_arena_reserve(size_t req_size, bool allow_large, mi_arena_id_t req_arena_id, mi_arena_id_t *arena_id)
350
{
351
if (_mi_preloading()) return false; // use OS only while pre loading
352
if (req_arena_id != _mi_arena_id_none()) return false;
353
354
const size_t arena_count = mi_atomic_load_acquire(&mi_arena_count);
355
if (arena_count > (MI_MAX_ARENAS - 4)) return false;
356
357
size_t arena_reserve = mi_option_get_size(mi_option_arena_reserve);
358
if (arena_reserve == 0) return false;
359
360
if (!_mi_os_has_virtual_reserve()) {
361
arena_reserve = arena_reserve/4; // be conservative if virtual reserve is not supported (for WASM for example)
362
}
363
arena_reserve = _mi_align_up(arena_reserve, MI_ARENA_BLOCK_SIZE);
364
if (arena_count >= 8 && arena_count <= 128) {
365
arena_reserve = ((size_t)1<<(arena_count/8)) * arena_reserve; // scale up the arena sizes exponentially
366
}
367
if (arena_reserve < req_size) return false; // should be able to at least handle the current allocation size
368
369
// commit eagerly?
370
bool arena_commit = false;
371
if (mi_option_get(mi_option_arena_eager_commit) == 2) { arena_commit = _mi_os_has_overcommit(); }
372
else if (mi_option_get(mi_option_arena_eager_commit) == 1) { arena_commit = true; }
373
374
return (mi_reserve_os_memory_ex(arena_reserve, arena_commit, allow_large, false /* exclusive? */, arena_id) == 0);
375
}
376
377
378
void* _mi_arena_alloc_aligned(size_t size, size_t alignment, size_t align_offset, bool commit, bool allow_large,
379
mi_arena_id_t req_arena_id, mi_memid_t* memid, mi_os_tld_t* tld)
380
{
381
mi_assert_internal(memid != NULL && tld != NULL);
382
mi_assert_internal(size > 0);
383
*memid = _mi_memid_none();
384
385
const int numa_node = _mi_os_numa_node(tld); // current numa node
386
387
// try to allocate in an arena if the alignment is small enough and the object is not too small (as for heap meta data)
388
if (!mi_option_is_enabled(mi_option_disallow_arena_alloc) || req_arena_id != _mi_arena_id_none()) { // is arena allocation allowed?
389
if (size >= MI_ARENA_MIN_OBJ_SIZE && alignment <= MI_SEGMENT_ALIGN && align_offset == 0) {
390
void* p = mi_arena_try_alloc(numa_node, size, alignment, commit, allow_large, req_arena_id, memid, tld);
391
if (p != NULL) return p;
392
393
// otherwise, try to first eagerly reserve a new arena
394
if (req_arena_id == _mi_arena_id_none()) {
395
mi_arena_id_t arena_id = 0;
396
if (mi_arena_reserve(size, allow_large, req_arena_id, &arena_id)) {
397
// and try allocate in there
398
mi_assert_internal(req_arena_id == _mi_arena_id_none());
399
p = mi_arena_try_alloc_at_id(arena_id, true, numa_node, size, alignment, commit, allow_large, req_arena_id, memid, tld);
400
if (p != NULL) return p;
401
}
402
}
403
}
404
}
405
406
// if we cannot use OS allocation, return NULL
407
if (mi_option_is_enabled(mi_option_disallow_os_alloc) || req_arena_id != _mi_arena_id_none()) {
408
errno = ENOMEM;
409
return NULL;
410
}
411
412
// finally, fall back to the OS
413
if (align_offset > 0) {
414
return _mi_os_alloc_aligned_at_offset(size, alignment, align_offset, commit, allow_large, memid, tld->stats);
415
}
416
else {
417
return _mi_os_alloc_aligned(size, alignment, commit, allow_large, memid, tld->stats);
418
}
419
}
420
421
void* _mi_arena_alloc(size_t size, bool commit, bool allow_large, mi_arena_id_t req_arena_id, mi_memid_t* memid, mi_os_tld_t* tld)
422
{
423
return _mi_arena_alloc_aligned(size, MI_ARENA_BLOCK_SIZE, 0, commit, allow_large, req_arena_id, memid, tld);
424
}
425
426
427
void* mi_arena_area(mi_arena_id_t arena_id, size_t* size) {
428
if (size != NULL) *size = 0;
429
size_t arena_index = mi_arena_id_index(arena_id);
430
if (arena_index >= MI_MAX_ARENAS) return NULL;
431
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[arena_index]);
432
if (arena == NULL) return NULL;
433
if (size != NULL) { *size = mi_arena_block_size(arena->block_count); }
434
return arena->start;
435
}
436
437
438
/* -----------------------------------------------------------
439
Arena purge
440
----------------------------------------------------------- */
441
442
static long mi_arena_purge_delay(void) {
443
// <0 = no purging allowed, 0=immediate purging, >0=milli-second delay
444
return (mi_option_get(mi_option_purge_delay) * mi_option_get(mi_option_arena_purge_mult));
445
}
446
447
// reset or decommit in an arena and update the committed/decommit bitmaps
448
// assumes we own the area (i.e. blocks_in_use is claimed by us)
449
static void mi_arena_purge(mi_arena_t* arena, size_t bitmap_idx, size_t blocks, mi_stats_t* stats) {
450
mi_assert_internal(arena->blocks_committed != NULL);
451
mi_assert_internal(arena->blocks_purge != NULL);
452
mi_assert_internal(!arena->memid.is_pinned);
453
const size_t size = mi_arena_block_size(blocks);
454
void* const p = mi_arena_block_start(arena, bitmap_idx);
455
bool needs_recommit;
456
if (_mi_bitmap_is_claimed_across(arena->blocks_committed, arena->field_count, blocks, bitmap_idx)) {
457
// all blocks are committed, we can purge freely
458
needs_recommit = _mi_os_purge(p, size, stats);
459
}
460
else {
461
// some blocks are not committed -- this can happen when a partially committed block is freed
462
// in `_mi_arena_free` and it is conservatively marked as uncommitted but still scheduled for a purge
463
// we need to ensure we do not try to reset (as that may be invalid for uncommitted memory),
464
// and also undo the decommit stats (as it was already adjusted)
465
mi_assert_internal(mi_option_is_enabled(mi_option_purge_decommits));
466
needs_recommit = _mi_os_purge_ex(p, size, false /* allow reset? */, stats);
467
if (needs_recommit) { _mi_stat_increase(&_mi_stats_main.committed, size); }
468
}
469
470
// clear the purged blocks
471
_mi_bitmap_unclaim_across(arena->blocks_purge, arena->field_count, blocks, bitmap_idx);
472
// update committed bitmap
473
if (needs_recommit) {
474
_mi_bitmap_unclaim_across(arena->blocks_committed, arena->field_count, blocks, bitmap_idx);
475
}
476
}
477
478
// Schedule a purge. This is usually delayed to avoid repeated decommit/commit calls.
479
// Note: assumes we (still) own the area as we may purge immediately
480
static void mi_arena_schedule_purge(mi_arena_t* arena, size_t bitmap_idx, size_t blocks, mi_stats_t* stats) {
481
mi_assert_internal(arena->blocks_purge != NULL);
482
const long delay = mi_arena_purge_delay();
483
if (delay < 0) return; // is purging allowed at all?
484
485
if (_mi_preloading() || delay == 0) {
486
// decommit directly
487
mi_arena_purge(arena, bitmap_idx, blocks, stats);
488
}
489
else {
490
// schedule decommit
491
mi_msecs_t expire = mi_atomic_loadi64_relaxed(&arena->purge_expire);
492
if (expire != 0) {
493
mi_atomic_addi64_acq_rel(&arena->purge_expire, (mi_msecs_t)(delay/10)); // add smallish extra delay
494
}
495
else {
496
mi_atomic_storei64_release(&arena->purge_expire, _mi_clock_now() + delay);
497
}
498
_mi_bitmap_claim_across(arena->blocks_purge, arena->field_count, blocks, bitmap_idx, NULL);
499
}
500
}
501
502
// purge a range of blocks
503
// return true if the full range was purged.
504
// assumes we own the area (i.e. blocks_in_use is claimed by us)
505
static bool mi_arena_purge_range(mi_arena_t* arena, size_t idx, size_t startidx, size_t bitlen, size_t purge, mi_stats_t* stats) {
506
const size_t endidx = startidx + bitlen;
507
size_t bitidx = startidx;
508
bool all_purged = false;
509
while (bitidx < endidx) {
510
// count consequetive ones in the purge mask
511
size_t count = 0;
512
while (bitidx + count < endidx && (purge & ((size_t)1 << (bitidx + count))) != 0) {
513
count++;
514
}
515
if (count > 0) {
516
// found range to be purged
517
const mi_bitmap_index_t range_idx = mi_bitmap_index_create(idx, bitidx);
518
mi_arena_purge(arena, range_idx, count, stats);
519
if (count == bitlen) {
520
all_purged = true;
521
}
522
}
523
bitidx += (count+1); // +1 to skip the zero bit (or end)
524
}
525
return all_purged;
526
}
527
528
// returns true if anything was purged
529
static bool mi_arena_try_purge(mi_arena_t* arena, mi_msecs_t now, bool force, mi_stats_t* stats)
530
{
531
if (arena->memid.is_pinned || arena->blocks_purge == NULL) return false;
532
mi_msecs_t expire = mi_atomic_loadi64_relaxed(&arena->purge_expire);
533
if (expire == 0) return false;
534
if (!force && expire > now) return false;
535
536
// reset expire (if not already set concurrently)
537
mi_atomic_casi64_strong_acq_rel(&arena->purge_expire, &expire, (mi_msecs_t)0);
538
539
// potential purges scheduled, walk through the bitmap
540
bool any_purged = false;
541
bool full_purge = true;
542
for (size_t i = 0; i < arena->field_count; i++) {
543
size_t purge = mi_atomic_load_relaxed(&arena->blocks_purge[i]);
544
if (purge != 0) {
545
size_t bitidx = 0;
546
while (bitidx < MI_BITMAP_FIELD_BITS) {
547
// find consequetive range of ones in the purge mask
548
size_t bitlen = 0;
549
while (bitidx + bitlen < MI_BITMAP_FIELD_BITS && (purge & ((size_t)1 << (bitidx + bitlen))) != 0) {
550
bitlen++;
551
}
552
// try to claim the longest range of corresponding in_use bits
553
const mi_bitmap_index_t bitmap_index = mi_bitmap_index_create(i, bitidx);
554
while( bitlen > 0 ) {
555
if (_mi_bitmap_try_claim(arena->blocks_inuse, arena->field_count, bitlen, bitmap_index)) {
556
break;
557
}
558
bitlen--;
559
}
560
// actual claimed bits at `in_use`
561
if (bitlen > 0) {
562
// read purge again now that we have the in_use bits
563
purge = mi_atomic_load_acquire(&arena->blocks_purge[i]);
564
if (!mi_arena_purge_range(arena, i, bitidx, bitlen, purge, stats)) {
565
full_purge = false;
566
}
567
any_purged = true;
568
// release the claimed `in_use` bits again
569
_mi_bitmap_unclaim(arena->blocks_inuse, arena->field_count, bitlen, bitmap_index);
570
}
571
bitidx += (bitlen+1); // +1 to skip the zero (or end)
572
} // while bitidx
573
} // purge != 0
574
}
575
// if not fully purged, make sure to purge again in the future
576
if (!full_purge) {
577
const long delay = mi_arena_purge_delay();
578
mi_msecs_t expected = 0;
579
mi_atomic_casi64_strong_acq_rel(&arena->purge_expire,&expected,_mi_clock_now() + delay);
580
}
581
return any_purged;
582
}
583
584
static void mi_arenas_try_purge( bool force, bool visit_all, mi_stats_t* stats ) {
585
if (_mi_preloading() || mi_arena_purge_delay() <= 0) return; // nothing will be scheduled
586
587
const size_t max_arena = mi_atomic_load_acquire(&mi_arena_count);
588
if (max_arena == 0) return;
589
590
// allow only one thread to purge at a time
591
static mi_atomic_guard_t purge_guard;
592
mi_atomic_guard(&purge_guard)
593
{
594
mi_msecs_t now = _mi_clock_now();
595
size_t max_purge_count = (visit_all ? max_arena : 1);
596
for (size_t i = 0; i < max_arena; i++) {
597
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[i]);
598
if (arena != NULL) {
599
if (mi_arena_try_purge(arena, now, force, stats)) {
600
if (max_purge_count <= 1) break;
601
max_purge_count--;
602
}
603
}
604
}
605
}
606
}
607
608
609
/* -----------------------------------------------------------
610
Arena free
611
----------------------------------------------------------- */
612
613
void _mi_arena_free(void* p, size_t size, size_t committed_size, mi_memid_t memid, mi_stats_t* stats) {
614
mi_assert_internal(size > 0 && stats != NULL);
615
mi_assert_internal(committed_size <= size);
616
if (p==NULL) return;
617
if (size==0) return;
618
const bool all_committed = (committed_size == size);
619
620
if (mi_memkind_is_os(memid.memkind)) {
621
// was a direct OS allocation, pass through
622
if (!all_committed && committed_size > 0) {
623
// if partially committed, adjust the committed stats (as `_mi_os_free` will increase decommit by the full size)
624
_mi_stat_decrease(&_mi_stats_main.committed, committed_size);
625
}
626
_mi_os_free(p, size, memid, stats);
627
}
628
else if (memid.memkind == MI_MEM_ARENA) {
629
// allocated in an arena
630
size_t arena_idx;
631
size_t bitmap_idx;
632
mi_arena_memid_indices(memid, &arena_idx, &bitmap_idx);
633
mi_assert_internal(arena_idx < MI_MAX_ARENAS);
634
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t,&mi_arenas[arena_idx]);
635
mi_assert_internal(arena != NULL);
636
const size_t blocks = mi_block_count_of_size(size);
637
638
// checks
639
if (arena == NULL) {
640
_mi_error_message(EINVAL, "trying to free from an invalid arena: %p, size %zu, memid: 0x%zx\n", p, size, memid);
641
return;
642
}
643
mi_assert_internal(arena->field_count > mi_bitmap_index_field(bitmap_idx));
644
if (arena->field_count <= mi_bitmap_index_field(bitmap_idx)) {
645
_mi_error_message(EINVAL, "trying to free from an invalid arena block: %p, size %zu, memid: 0x%zx\n", p, size, memid);
646
return;
647
}
648
649
// need to set all memory to undefined as some parts may still be marked as no_access (like padding etc.)
650
mi_track_mem_undefined(p,size);
651
652
// potentially decommit
653
if (arena->memid.is_pinned || arena->blocks_committed == NULL) {
654
mi_assert_internal(all_committed);
655
}
656
else {
657
mi_assert_internal(arena->blocks_committed != NULL);
658
mi_assert_internal(arena->blocks_purge != NULL);
659
660
if (!all_committed) {
661
// mark the entire range as no longer committed (so we recommit the full range when re-using)
662
_mi_bitmap_unclaim_across(arena->blocks_committed, arena->field_count, blocks, bitmap_idx);
663
mi_track_mem_noaccess(p,size);
664
if (committed_size > 0) {
665
// if partially committed, adjust the committed stats (is it will be recommitted when re-using)
666
// in the delayed purge, we now need to not count a decommit if the range is not marked as committed.
667
_mi_stat_decrease(&_mi_stats_main.committed, committed_size);
668
}
669
// note: if not all committed, it may be that the purge will reset/decommit the entire range
670
// that contains already decommitted parts. Since purge consistently uses reset or decommit that
671
// works (as we should never reset decommitted parts).
672
}
673
// (delay) purge the entire range
674
mi_arena_schedule_purge(arena, bitmap_idx, blocks, stats);
675
}
676
677
// and make it available to others again
678
bool all_inuse = _mi_bitmap_unclaim_across(arena->blocks_inuse, arena->field_count, blocks, bitmap_idx);
679
if (!all_inuse) {
680
_mi_error_message(EAGAIN, "trying to free an already freed arena block: %p, size %zu\n", p, size);
681
return;
682
};
683
}
684
else {
685
// arena was none, external, or static; nothing to do
686
mi_assert_internal(memid.memkind < MI_MEM_OS);
687
}
688
689
// purge expired decommits
690
mi_arenas_try_purge(false, false, stats);
691
}
692
693
// destroy owned arenas; this is unsafe and should only be done using `mi_option_destroy_on_exit`
694
// for dynamic libraries that are unloaded and need to release all their allocated memory.
695
static void mi_arenas_unsafe_destroy(void) {
696
const size_t max_arena = mi_atomic_load_relaxed(&mi_arena_count);
697
size_t new_max_arena = 0;
698
for (size_t i = 0; i < max_arena; i++) {
699
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[i]);
700
if (arena != NULL) {
701
if (arena->start != NULL && mi_memkind_is_os(arena->memid.memkind)) {
702
mi_atomic_store_ptr_release(mi_arena_t, &mi_arenas[i], NULL);
703
_mi_os_free(arena->start, mi_arena_size(arena), arena->memid, &_mi_stats_main);
704
}
705
else {
706
new_max_arena = i;
707
}
708
mi_arena_meta_free(arena, arena->meta_memid, arena->meta_size, &_mi_stats_main);
709
}
710
}
711
712
// try to lower the max arena.
713
size_t expected = max_arena;
714
mi_atomic_cas_strong_acq_rel(&mi_arena_count, &expected, new_max_arena);
715
}
716
717
// Purge the arenas; if `force_purge` is true, amenable parts are purged even if not yet expired
718
void _mi_arenas_collect(bool force_purge, mi_stats_t* stats) {
719
mi_arenas_try_purge(force_purge, force_purge /* visit all? */, stats);
720
}
721
722
// destroy owned arenas; this is unsafe and should only be done using `mi_option_destroy_on_exit`
723
// for dynamic libraries that are unloaded and need to release all their allocated memory.
724
void _mi_arena_unsafe_destroy_all(mi_stats_t* stats) {
725
mi_arenas_unsafe_destroy();
726
_mi_arenas_collect(true /* force purge */, stats); // purge non-owned arenas
727
}
728
729
// Is a pointer inside any of our arenas?
730
bool _mi_arena_contains(const void* p) {
731
const size_t max_arena = mi_atomic_load_relaxed(&mi_arena_count);
732
for (size_t i = 0; i < max_arena; i++) {
733
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[i]);
734
if (arena != NULL && arena->start <= (const uint8_t*)p && arena->start + mi_arena_block_size(arena->block_count) > (const uint8_t*)p) {
735
return true;
736
}
737
}
738
return false;
739
}
740
741
/* -----------------------------------------------------------
742
Abandoned blocks/segments.
743
This is used to atomically abandon/reclaim segments
744
(and crosses the arena API but it is convenient to have here).
745
Abandoned segments still have live blocks; they get reclaimed
746
when a thread frees a block in it, or when a thread needs a fresh
747
segment; these threads scan the abandoned segments through
748
the arena bitmaps.
749
----------------------------------------------------------- */
750
751
// Maintain a count of all abandoned segments
752
static mi_decl_cache_align _Atomic(size_t)abandoned_count;
753
754
size_t _mi_arena_segment_abandoned_count(void) {
755
return mi_atomic_load_relaxed(&abandoned_count);
756
}
757
758
// reclaim a specific abandoned segment; `true` on success.
759
// sets the thread_id.
760
bool _mi_arena_segment_clear_abandoned(mi_segment_t* segment )
761
{
762
if (segment->memid.memkind != MI_MEM_ARENA) {
763
// not in an arena, consider it un-abandoned now.
764
// but we need to still claim it atomically -- we use the thread_id for that.
765
size_t expected = 0;
766
if (mi_atomic_cas_strong_acq_rel(&segment->thread_id, &expected, _mi_thread_id())) {
767
mi_atomic_decrement_relaxed(&abandoned_count);
768
return true;
769
}
770
else {
771
return false;
772
}
773
}
774
// arena segment: use the blocks_abandoned bitmap.
775
size_t arena_idx;
776
size_t bitmap_idx;
777
mi_arena_memid_indices(segment->memid, &arena_idx, &bitmap_idx);
778
mi_assert_internal(arena_idx < MI_MAX_ARENAS);
779
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[arena_idx]);
780
mi_assert_internal(arena != NULL);
781
bool was_marked = _mi_bitmap_unclaim(arena->blocks_abandoned, arena->field_count, 1, bitmap_idx);
782
if (was_marked) {
783
mi_assert_internal(mi_atomic_load_relaxed(&segment->thread_id) == 0);
784
mi_atomic_decrement_relaxed(&abandoned_count);
785
mi_atomic_store_release(&segment->thread_id, _mi_thread_id());
786
}
787
// mi_assert_internal(was_marked);
788
mi_assert_internal(!was_marked || _mi_bitmap_is_claimed(arena->blocks_inuse, arena->field_count, 1, bitmap_idx));
789
//mi_assert_internal(arena->blocks_committed == NULL || _mi_bitmap_is_claimed(arena->blocks_committed, arena->field_count, 1, bitmap_idx));
790
return was_marked;
791
}
792
793
// mark a specific segment as abandoned
794
// clears the thread_id.
795
void _mi_arena_segment_mark_abandoned(mi_segment_t* segment)
796
{
797
mi_atomic_store_release(&segment->thread_id, 0);
798
mi_assert_internal(segment->used == segment->abandoned);
799
if (segment->memid.memkind != MI_MEM_ARENA) {
800
// not in an arena; count it as abandoned and return
801
mi_atomic_increment_relaxed(&abandoned_count);
802
return;
803
}
804
size_t arena_idx;
805
size_t bitmap_idx;
806
mi_arena_memid_indices(segment->memid, &arena_idx, &bitmap_idx);
807
mi_assert_internal(arena_idx < MI_MAX_ARENAS);
808
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[arena_idx]);
809
mi_assert_internal(arena != NULL);
810
const bool was_unmarked = _mi_bitmap_claim(arena->blocks_abandoned, arena->field_count, 1, bitmap_idx, NULL);
811
if (was_unmarked) { mi_atomic_increment_relaxed(&abandoned_count); }
812
mi_assert_internal(was_unmarked);
813
mi_assert_internal(_mi_bitmap_is_claimed(arena->blocks_inuse, arena->field_count, 1, bitmap_idx));
814
}
815
816
// start a cursor at a randomized arena
817
void _mi_arena_field_cursor_init(mi_heap_t* heap, mi_arena_field_cursor_t* current) {
818
const size_t max_arena = mi_atomic_load_relaxed(&mi_arena_count);
819
current->start = (max_arena == 0 ? 0 : (mi_arena_id_t)( _mi_heap_random_next(heap) % max_arena));
820
current->count = 0;
821
current->bitmap_idx = 0;
822
}
823
824
// reclaim abandoned segments
825
// this does not set the thread id (so it appears as still abandoned)
826
mi_segment_t* _mi_arena_segment_clear_abandoned_next(mi_arena_field_cursor_t* previous )
827
{
828
const int max_arena = (int)mi_atomic_load_relaxed(&mi_arena_count);
829
if (max_arena <= 0 || mi_atomic_load_relaxed(&abandoned_count) == 0) return NULL;
830
831
int count = previous->count;
832
size_t field_idx = mi_bitmap_index_field(previous->bitmap_idx);
833
size_t bit_idx = mi_bitmap_index_bit_in_field(previous->bitmap_idx) + 1;
834
// visit arena's (from previous)
835
for (; count < max_arena; count++, field_idx = 0, bit_idx = 0) {
836
mi_arena_id_t arena_idx = previous->start + count;
837
if (arena_idx >= max_arena) { arena_idx = arena_idx % max_arena; } // wrap around
838
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[arena_idx]);
839
if (arena != NULL) {
840
// visit the abandoned fields (starting at previous_idx)
841
for ( ; field_idx < arena->field_count; field_idx++, bit_idx = 0) {
842
size_t field = mi_atomic_load_relaxed(&arena->blocks_abandoned[field_idx]);
843
if mi_unlikely(field != 0) { // skip zero fields quickly
844
// visit each set bit in the field (todo: maybe use `ctz` here?)
845
for ( ; bit_idx < MI_BITMAP_FIELD_BITS; bit_idx++) {
846
// pre-check if the bit is set
847
size_t mask = ((size_t)1 << bit_idx);
848
if mi_unlikely((field & mask) == mask) {
849
mi_bitmap_index_t bitmap_idx = mi_bitmap_index_create(field_idx, bit_idx);
850
// try to reclaim it atomically
851
if (_mi_bitmap_unclaim(arena->blocks_abandoned, arena->field_count, 1, bitmap_idx)) {
852
mi_atomic_decrement_relaxed(&abandoned_count);
853
previous->bitmap_idx = bitmap_idx;
854
previous->count = count;
855
mi_assert_internal(_mi_bitmap_is_claimed(arena->blocks_inuse, arena->field_count, 1, bitmap_idx));
856
mi_segment_t* segment = (mi_segment_t*)mi_arena_block_start(arena, bitmap_idx);
857
mi_assert_internal(mi_atomic_load_relaxed(&segment->thread_id) == 0);
858
//mi_assert_internal(arena->blocks_committed == NULL || _mi_bitmap_is_claimed(arena->blocks_committed, arena->field_count, 1, bitmap_idx));
859
return segment;
860
}
861
}
862
}
863
}
864
}
865
}
866
}
867
// no more found
868
previous->bitmap_idx = 0;
869
previous->count = 0;
870
return NULL;
871
}
872
873
874
/* -----------------------------------------------------------
875
Add an arena.
876
----------------------------------------------------------- */
877
878
static bool mi_arena_add(mi_arena_t* arena, mi_arena_id_t* arena_id, mi_stats_t* stats) {
879
mi_assert_internal(arena != NULL);
880
mi_assert_internal((uintptr_t)mi_atomic_load_ptr_relaxed(uint8_t,&arena->start) % MI_SEGMENT_ALIGN == 0);
881
mi_assert_internal(arena->block_count > 0);
882
if (arena_id != NULL) { *arena_id = -1; }
883
884
size_t i = mi_atomic_increment_acq_rel(&mi_arena_count);
885
if (i >= MI_MAX_ARENAS) {
886
mi_atomic_decrement_acq_rel(&mi_arena_count);
887
return false;
888
}
889
_mi_stat_counter_increase(&stats->arena_count,1);
890
arena->id = mi_arena_id_create(i);
891
mi_atomic_store_ptr_release(mi_arena_t,&mi_arenas[i], arena);
892
if (arena_id != NULL) { *arena_id = arena->id; }
893
return true;
894
}
895
896
static bool mi_manage_os_memory_ex2(void* start, size_t size, bool is_large, int numa_node, bool exclusive, mi_memid_t memid, mi_arena_id_t* arena_id) mi_attr_noexcept
897
{
898
if (arena_id != NULL) *arena_id = _mi_arena_id_none();
899
if (size < MI_ARENA_BLOCK_SIZE) return false;
900
901
if (is_large) {
902
mi_assert_internal(memid.initially_committed && memid.is_pinned);
903
}
904
905
const size_t bcount = size / MI_ARENA_BLOCK_SIZE;
906
const size_t fields = _mi_divide_up(bcount, MI_BITMAP_FIELD_BITS);
907
const size_t bitmaps = (memid.is_pinned ? 3 : 5);
908
const size_t asize = sizeof(mi_arena_t) + (bitmaps*fields*sizeof(mi_bitmap_field_t));
909
mi_memid_t meta_memid;
910
mi_arena_t* arena = (mi_arena_t*)mi_arena_meta_zalloc(asize, &meta_memid, &_mi_stats_main); // TODO: can we avoid allocating from the OS?
911
if (arena == NULL) return false;
912
913
// already zero'd due to zalloc
914
// _mi_memzero(arena, asize);
915
arena->id = _mi_arena_id_none();
916
arena->memid = memid;
917
arena->exclusive = exclusive;
918
arena->meta_size = asize;
919
arena->meta_memid = meta_memid;
920
arena->block_count = bcount;
921
arena->field_count = fields;
922
arena->start = (uint8_t*)start;
923
arena->numa_node = numa_node; // TODO: or get the current numa node if -1? (now it allows anyone to allocate on -1)
924
arena->is_large = is_large;
925
arena->purge_expire = 0;
926
arena->search_idx = 0;
927
// consequetive bitmaps
928
arena->blocks_dirty = &arena->blocks_inuse[fields]; // just after inuse bitmap
929
arena->blocks_abandoned = &arena->blocks_inuse[2 * fields]; // just after dirty bitmap
930
arena->blocks_committed = (arena->memid.is_pinned ? NULL : &arena->blocks_inuse[3*fields]); // just after abandoned bitmap
931
arena->blocks_purge = (arena->memid.is_pinned ? NULL : &arena->blocks_inuse[4*fields]); // just after committed bitmap
932
// initialize committed bitmap?
933
if (arena->blocks_committed != NULL && arena->memid.initially_committed) {
934
memset((void*)arena->blocks_committed, 0xFF, fields*sizeof(mi_bitmap_field_t)); // cast to void* to avoid atomic warning
935
}
936
937
// and claim leftover blocks if needed (so we never allocate there)
938
ptrdiff_t post = (fields * MI_BITMAP_FIELD_BITS) - bcount;
939
mi_assert_internal(post >= 0);
940
if (post > 0) {
941
// don't use leftover bits at the end
942
mi_bitmap_index_t postidx = mi_bitmap_index_create(fields - 1, MI_BITMAP_FIELD_BITS - post);
943
_mi_bitmap_claim(arena->blocks_inuse, fields, post, postidx, NULL);
944
}
945
return mi_arena_add(arena, arena_id, &_mi_stats_main);
946
947
}
948
949
bool mi_manage_os_memory_ex(void* start, size_t size, bool is_committed, bool is_large, bool is_zero, int numa_node, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept {
950
mi_memid_t memid = _mi_memid_create(MI_MEM_EXTERNAL);
951
memid.initially_committed = is_committed;
952
memid.initially_zero = is_zero;
953
memid.is_pinned = is_large;
954
return mi_manage_os_memory_ex2(start,size,is_large,numa_node,exclusive,memid, arena_id);
955
}
956
957
// Reserve a range of regular OS memory
958
int mi_reserve_os_memory_ex(size_t size, bool commit, bool allow_large, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept {
959
if (arena_id != NULL) *arena_id = _mi_arena_id_none();
960
size = _mi_align_up(size, MI_ARENA_BLOCK_SIZE); // at least one block
961
mi_memid_t memid;
962
void* start = _mi_os_alloc_aligned(size, MI_SEGMENT_ALIGN, commit, allow_large, &memid, &_mi_stats_main);
963
if (start == NULL) return ENOMEM;
964
const bool is_large = memid.is_pinned; // todo: use separate is_large field?
965
if (!mi_manage_os_memory_ex2(start, size, is_large, -1 /* numa node */, exclusive, memid, arena_id)) {
966
_mi_os_free_ex(start, size, commit, memid, &_mi_stats_main);
967
_mi_verbose_message("failed to reserve %zu KiB memory\n", _mi_divide_up(size, 1024));
968
return ENOMEM;
969
}
970
_mi_verbose_message("reserved %zu KiB memory%s\n", _mi_divide_up(size, 1024), is_large ? " (in large os pages)" : "");
971
return 0;
972
}
973
974
975
// Manage a range of regular OS memory
976
bool mi_manage_os_memory(void* start, size_t size, bool is_committed, bool is_large, bool is_zero, int numa_node) mi_attr_noexcept {
977
return mi_manage_os_memory_ex(start, size, is_committed, is_large, is_zero, numa_node, false /* exclusive? */, NULL);
978
}
979
980
// Reserve a range of regular OS memory
981
int mi_reserve_os_memory(size_t size, bool commit, bool allow_large) mi_attr_noexcept {
982
return mi_reserve_os_memory_ex(size, commit, allow_large, false, NULL);
983
}
984
985
986
/* -----------------------------------------------------------
987
Debugging
988
----------------------------------------------------------- */
989
990
static size_t mi_debug_show_bitmap(const char* prefix, const char* header, size_t block_count, mi_bitmap_field_t* fields, size_t field_count ) {
991
_mi_verbose_message("%s%s:\n", prefix, header);
992
size_t bcount = 0;
993
size_t inuse_count = 0;
994
for (size_t i = 0; i < field_count; i++) {
995
char buf[MI_BITMAP_FIELD_BITS + 1];
996
uintptr_t field = mi_atomic_load_relaxed(&fields[i]);
997
for (size_t bit = 0; bit < MI_BITMAP_FIELD_BITS; bit++, bcount++) {
998
if (bcount < block_count) {
999
bool inuse = ((((uintptr_t)1 << bit) & field) != 0);
1000
if (inuse) inuse_count++;
1001
buf[bit] = (inuse ? 'x' : '.');
1002
}
1003
else {
1004
buf[bit] = ' ';
1005
}
1006
}
1007
buf[MI_BITMAP_FIELD_BITS] = 0;
1008
_mi_verbose_message("%s %s\n", prefix, buf);
1009
}
1010
_mi_verbose_message("%s total ('x'): %zu\n", prefix, inuse_count);
1011
return inuse_count;
1012
}
1013
1014
void mi_debug_show_arenas(bool show_inuse, bool show_abandoned, bool show_purge) mi_attr_noexcept {
1015
size_t max_arenas = mi_atomic_load_relaxed(&mi_arena_count);
1016
size_t inuse_total = 0;
1017
size_t abandoned_total = 0;
1018
size_t purge_total = 0;
1019
for (size_t i = 0; i < max_arenas; i++) {
1020
mi_arena_t* arena = mi_atomic_load_ptr_relaxed(mi_arena_t, &mi_arenas[i]);
1021
if (arena == NULL) break;
1022
_mi_verbose_message("arena %zu: %zu blocks of size %zuMiB (in %zu fields) %s\n", i, arena->block_count, MI_ARENA_BLOCK_SIZE / MI_MiB, arena->field_count, (arena->memid.is_pinned ? ", pinned" : ""));
1023
if (show_inuse) {
1024
inuse_total += mi_debug_show_bitmap(" ", "inuse blocks", arena->block_count, arena->blocks_inuse, arena->field_count);
1025
}
1026
if (arena->blocks_committed != NULL) {
1027
mi_debug_show_bitmap(" ", "committed blocks", arena->block_count, arena->blocks_committed, arena->field_count);
1028
}
1029
if (show_abandoned) {
1030
abandoned_total += mi_debug_show_bitmap(" ", "abandoned blocks", arena->block_count, arena->blocks_abandoned, arena->field_count);
1031
}
1032
if (show_purge && arena->blocks_purge != NULL) {
1033
purge_total += mi_debug_show_bitmap(" ", "purgeable blocks", arena->block_count, arena->blocks_purge, arena->field_count);
1034
}
1035
}
1036
if (show_inuse) _mi_verbose_message("total inuse blocks : %zu\n", inuse_total);
1037
if (show_abandoned) _mi_verbose_message("total abandoned blocks: %zu\n", abandoned_total);
1038
if (show_purge) _mi_verbose_message("total purgeable blocks: %zu\n", purge_total);
1039
}
1040
1041
1042
/* -----------------------------------------------------------
1043
Reserve a huge page arena.
1044
----------------------------------------------------------- */
1045
// reserve at a specific numa node
1046
int mi_reserve_huge_os_pages_at_ex(size_t pages, int numa_node, size_t timeout_msecs, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept {
1047
if (arena_id != NULL) *arena_id = -1;
1048
if (pages==0) return 0;
1049
if (numa_node < -1) numa_node = -1;
1050
if (numa_node >= 0) numa_node = numa_node % _mi_os_numa_node_count();
1051
size_t hsize = 0;
1052
size_t pages_reserved = 0;
1053
mi_memid_t memid;
1054
void* p = _mi_os_alloc_huge_os_pages(pages, numa_node, timeout_msecs, &pages_reserved, &hsize, &memid);
1055
if (p==NULL || pages_reserved==0) {
1056
_mi_warning_message("failed to reserve %zu GiB huge pages\n", pages);
1057
return ENOMEM;
1058
}
1059
_mi_verbose_message("numa node %i: reserved %zu GiB huge pages (of the %zu GiB requested)\n", numa_node, pages_reserved, pages);
1060
1061
if (!mi_manage_os_memory_ex2(p, hsize, true, numa_node, exclusive, memid, arena_id)) {
1062
_mi_os_free(p, hsize, memid, &_mi_stats_main);
1063
return ENOMEM;
1064
}
1065
return 0;
1066
}
1067
1068
int mi_reserve_huge_os_pages_at(size_t pages, int numa_node, size_t timeout_msecs) mi_attr_noexcept {
1069
return mi_reserve_huge_os_pages_at_ex(pages, numa_node, timeout_msecs, false, NULL);
1070
}
1071
1072
// reserve huge pages evenly among the given number of numa nodes (or use the available ones as detected)
1073
int mi_reserve_huge_os_pages_interleave(size_t pages, size_t numa_nodes, size_t timeout_msecs) mi_attr_noexcept {
1074
if (pages == 0) return 0;
1075
1076
// pages per numa node
1077
size_t numa_count = (numa_nodes > 0 ? numa_nodes : _mi_os_numa_node_count());
1078
if (numa_count <= 0) numa_count = 1;
1079
const size_t pages_per = pages / numa_count;
1080
const size_t pages_mod = pages % numa_count;
1081
const size_t timeout_per = (timeout_msecs==0 ? 0 : (timeout_msecs / numa_count) + 50);
1082
1083
// reserve evenly among numa nodes
1084
for (size_t numa_node = 0; numa_node < numa_count && pages > 0; numa_node++) {
1085
size_t node_pages = pages_per; // can be 0
1086
if (numa_node < pages_mod) node_pages++;
1087
int err = mi_reserve_huge_os_pages_at(node_pages, (int)numa_node, timeout_per);
1088
if (err) return err;
1089
if (pages < node_pages) {
1090
pages = 0;
1091
}
1092
else {
1093
pages -= node_pages;
1094
}
1095
}
1096
1097
return 0;
1098
}
1099
1100
int mi_reserve_huge_os_pages(size_t pages, double max_secs, size_t* pages_reserved) mi_attr_noexcept {
1101
MI_UNUSED(max_secs);
1102
_mi_warning_message("mi_reserve_huge_os_pages is deprecated: use mi_reserve_huge_os_pages_interleave/at instead\n");
1103
if (pages_reserved != NULL) *pages_reserved = 0;
1104
int err = mi_reserve_huge_os_pages_interleave(pages, 0, (size_t)(max_secs * 1000.0));
1105
if (err==0 && pages_reserved!=NULL) *pages_reserved = pages;
1106
return err;
1107
}
1108
1109
1110