Skip to content

Commit 476d469

Browse files
committed
limit purgeing to one purge cycle per purge delay
1 parent e3ebebb commit 476d469

File tree

2 files changed

+56
-39
lines changed

2 files changed

+56
-39
lines changed

include/mimalloc/atomic.h

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -421,9 +421,8 @@ static inline void mi_atomic_yield(void) {
421421
static inline bool mi_lock_try_acquire(mi_lock_t* lock) {
422422
return TryAcquireSRWLockExclusive(lock);
423423
}
424-
static inline bool mi_lock_acquire(mi_lock_t* lock) {
424+
static inline void mi_lock_acquire(mi_lock_t* lock) {
425425
AcquireSRWLockExclusive(lock);
426-
return true;
427426
}
428427
static inline void mi_lock_release(mi_lock_t* lock) {
429428
ReleaseSRWLockExclusive(lock);
@@ -432,32 +431,28 @@ static inline void mi_lock_init(mi_lock_t* lock) {
432431
InitializeSRWLock(lock);
433432
}
434433
static inline void mi_lock_done(mi_lock_t* lock) {
435-
// nothing
434+
(void)(lock);
436435
}
437436

438437
#else
439438
#define mi_lock_t CRITICAL_SECTION
440439
441440
static inline bool mi_lock_try_acquire(mi_lock_t* lock) {
442441
return TryEnterCriticalSection(lock);
443-
444442
}
445443
static inline void mi_lock_acquire(mi_lock_t* lock) {
446444
EnterCriticalSection(lock);
447-
448445
}
449446
static inline void mi_lock_release(mi_lock_t* lock) {
450447
LeaveCriticalSection(lock);
451-
452448
}
453449
static inline void mi_lock_init(mi_lock_t* lock) {
454450
InitializeCriticalSection(lock);
455-
456451
}
457452
static inline void mi_lock_done(mi_lock_t* lock) {
458453
DeleteCriticalSection(lock);
459-
460454
}
455+
461456
#endif
462457

463458
#elif defined(MI_USE_PTHREADS)
@@ -467,8 +462,11 @@ static inline void mi_lock_done(mi_lock_t* lock) {
467462
static inline bool mi_lock_try_acquire(mi_lock_t* lock) {
468463
return (pthread_mutex_trylock(lock) == 0);
469464
}
470-
static inline bool mi_lock_acquire(mi_lock_t* lock) {
471-
return (pthread_mutex_lock(lock) == 0);
465+
static inline void mi_lock_acquire(mi_lock_t* lock) {
466+
const int err = pthread_mutex_lock(lock);
467+
if (err != 0) {
468+
mi_error_message(EFAULT, "internal error: lock cannot be acquired\n");
469+
}
472470
}
473471
static inline void mi_lock_release(mi_lock_t* lock) {
474472
pthread_mutex_unlock(lock);
@@ -488,9 +486,8 @@ static inline void mi_lock_done(mi_lock_t* lock) {
488486
static inline bool mi_lock_try_acquire(mi_lock_t* lock) {
489487
return lock->try_lock();
490488
}
491-
static inline bool mi_lock_acquire(mi_lock_t* lock) {
489+
static inline void mi_lock_acquire(mi_lock_t* lock) {
492490
lock->lock();
493-
return true;
494491
}
495492
static inline void mi_lock_release(mi_lock_t* lock) {
496493
lock->unlock();
@@ -513,12 +510,11 @@ static inline bool mi_lock_try_acquire(mi_lock_t* lock) {
513510
uintptr_t expected = 0;
514511
return mi_atomic_cas_strong_acq_rel(lock, &expected, (uintptr_t)1);
515512
}
516-
static inline bool mi_lock_acquire(mi_lock_t* lock) {
513+
static inline void mi_lock_acquire(mi_lock_t* lock) {
517514
for (int i = 0; i < 1000; i++) { // for at most 1000 tries?
518-
if (mi_lock_try_acquire(lock)) return true;
515+
if (mi_lock_try_acquire(lock)) return;
519516
mi_atomic_yield();
520517
}
521-
return true;
522518
}
523519
static inline void mi_lock_release(mi_lock_t* lock) {
524520
mi_atomic_store_release(lock, (uintptr_t)0);

src/arena.c

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ The arena allocation needs to be thread safe and we use an atomic bitmap to allo
3333
typedef struct mi_arena_s {
3434
mi_arena_id_t id; // arena id; 0 for non-specific
3535
mi_memid_t memid; // memid of the memory area
36-
_Atomic(uint8_t*)start; // the start of the memory area
36+
_Atomic(uint8_t*) start; // the start of the memory area
3737
size_t block_count; // size of the area in arena blocks (of `MI_ARENA_BLOCK_SIZE`)
3838
size_t field_count; // number of bitmap fields (where `field_count * MI_BITMAP_FIELD_BITS >= block_count`)
3939
size_t meta_size; // size of the arena structure itself (including its bitmaps)
@@ -42,12 +42,13 @@ typedef struct mi_arena_s {
4242
bool exclusive; // only allow allocations if specifically for this arena
4343
bool is_large; // memory area consists of large- or huge OS pages (always committed)
4444
mi_lock_t abandoned_visit_lock; // lock is only used when abandoned segments are being visited
45-
_Atomic(size_t)search_idx; // optimization to start the search for free blocks
46-
_Atomic(mi_msecs_t)purge_expire; // expiration time when blocks should be decommitted from `blocks_decommit`.
47-
mi_bitmap_field_t* blocks_dirty; // are the blocks potentially non-zero?
48-
mi_bitmap_field_t* blocks_committed; // are the blocks committed? (can be NULL for memory that cannot be decommitted)
49-
mi_bitmap_field_t* blocks_purge; // blocks that can be (reset) decommitted. (can be NULL for memory that cannot be (reset) decommitted)
50-
mi_bitmap_field_t* blocks_abandoned; // blocks that start with an abandoned segment. (This crosses API's but it is convenient to have here)
45+
_Atomic(size_t) search_idx; // optimization to start the search for free blocks
46+
_Atomic(mi_msecs_t) purge_expire; // expiration time when blocks should be purged from `blocks_purge`.
47+
48+
mi_bitmap_field_t* blocks_dirty; // are the blocks potentially non-zero?
49+
mi_bitmap_field_t* blocks_committed; // are the blocks committed? (can be NULL for memory that cannot be decommitted)
50+
mi_bitmap_field_t* blocks_purge; // blocks that can be (reset) decommitted. (can be NULL for memory that cannot be (reset) decommitted)
51+
mi_bitmap_field_t* blocks_abandoned; // blocks that start with an abandoned segment. (This crosses API's but it is convenient to have here)
5152
mi_bitmap_field_t blocks_inuse[1]; // in-place bitmap of in-use blocks (of size `field_count`)
5253
// do not add further fields here as the dirty, committed, purged, and abandoned bitmaps follow the inuse bitmap fields.
5354
} mi_arena_t;
@@ -60,6 +61,7 @@ typedef struct mi_arena_s {
6061
// The available arenas
6162
static mi_decl_cache_align _Atomic(mi_arena_t*) mi_arenas[MI_MAX_ARENAS];
6263
static mi_decl_cache_align _Atomic(size_t) mi_arena_count; // = 0
64+
static mi_decl_cache_align _Atomic(int64_t) mi_arenas_purge_expire; // set if there exist purgeable arenas
6365

6466
#define MI_IN_ARENA_C
6567
#include "arena-abandon.c"
@@ -349,11 +351,10 @@ static mi_decl_noinline void* mi_arena_try_alloc(int numa_node, size_t size, siz
349351
}
350352

351353
// try to reserve a fresh arena space
352-
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)
354+
static bool mi_arena_reserve(size_t req_size, bool allow_large, mi_arena_id_t *arena_id)
353355
{
354356
if (_mi_preloading()) return false; // use OS only while pre loading
355-
if (req_arena_id != _mi_arena_id_none()) return false;
356-
357+
357358
const size_t arena_count = mi_atomic_load_acquire(&mi_arena_count);
358359
if (arena_count > (MI_MAX_ARENAS - 4)) return false;
359360

@@ -403,7 +404,7 @@ void* _mi_arena_alloc_aligned(size_t size, size_t alignment, size_t align_offset
403404
// otherwise, try to first eagerly reserve a new arena
404405
if (req_arena_id == _mi_arena_id_none()) {
405406
mi_arena_id_t arena_id = 0;
406-
if (mi_arena_reserve(size, allow_large, req_arena_id, &arena_id)) {
407+
if (mi_arena_reserve(size, allow_large, &arena_id)) {
407408
// and try allocate in there
408409
mi_assert_internal(req_arena_id == _mi_arena_id_none());
409410
p = mi_arena_try_alloc_at_id(arena_id, true, numa_node, size, alignment, commit, allow_large, req_arena_id, memid);
@@ -497,13 +498,16 @@ static void mi_arena_schedule_purge(mi_arena_t* arena, size_t bitmap_idx, size_t
497498
mi_arena_purge(arena, bitmap_idx, blocks);
498499
}
499500
else {
500-
// schedule decommit
501-
mi_msecs_t expire = mi_atomic_loadi64_relaxed(&arena->purge_expire);
502-
if (expire != 0) {
503-
mi_atomic_addi64_acq_rel(&arena->purge_expire, (mi_msecs_t)(delay/10)); // add smallish extra delay
501+
// schedule purge
502+
const mi_msecs_t expire = _mi_clock_now() + delay;
503+
mi_msecs_t expire0 = 0;
504+
if (mi_atomic_casi64_strong_acq_rel(&arena->purge_expire, &expire0, expire)) {
505+
// expiration was not yet set
506+
// maybe set the global arenas expire as well (if it wasn't set already)
507+
mi_atomic_casi64_strong_acq_rel(&mi_arenas_purge_expire, &expire0, expire);
504508
}
505509
else {
506-
mi_atomic_storei64_release(&arena->purge_expire, _mi_clock_now() + delay);
510+
// already an expiration was set
507511
}
508512
_mi_bitmap_claim_across(arena->blocks_purge, arena->field_count, blocks, bitmap_idx, NULL);
509513
}
@@ -538,14 +542,16 @@ static bool mi_arena_purge_range(mi_arena_t* arena, size_t idx, size_t startidx,
538542
// returns true if anything was purged
539543
static bool mi_arena_try_purge(mi_arena_t* arena, mi_msecs_t now, bool force)
540544
{
541-
if (arena->memid.is_pinned || arena->blocks_purge == NULL) return false;
545+
// check pre-conditions
546+
if (arena->memid.is_pinned) return false;
547+
548+
// expired yet?
542549
mi_msecs_t expire = mi_atomic_loadi64_relaxed(&arena->purge_expire);
543-
if (expire == 0) return false;
544-
if (!force && expire > now) return false;
550+
if (!force && (expire == 0 || expire > now)) return false;
545551

546552
// reset expire (if not already set concurrently)
547553
mi_atomic_casi64_strong_acq_rel(&arena->purge_expire, &expire, (mi_msecs_t)0);
548-
554+
549555
// potential purges scheduled, walk through the bitmap
550556
bool any_purged = false;
551557
bool full_purge = true;
@@ -592,27 +598,42 @@ static bool mi_arena_try_purge(mi_arena_t* arena, mi_msecs_t now, bool force)
592598
return any_purged;
593599
}
594600

595-
static void mi_arenas_try_purge( bool force, bool visit_all ) {
601+
static void mi_arenas_try_purge( bool force, bool visit_all )
602+
{
596603
if (_mi_preloading() || mi_arena_purge_delay() <= 0) return; // nothing will be scheduled
597604

605+
// check if any arena needs purging?
606+
const mi_msecs_t now = _mi_clock_now();
607+
mi_msecs_t arenas_expire = mi_atomic_load_acquire(&mi_arenas_purge_expire);
608+
if (!force && (arenas_expire == 0 || arenas_expire < now)) return;
609+
598610
const size_t max_arena = mi_atomic_load_acquire(&mi_arena_count);
599611
if (max_arena == 0) return;
600612

601613
// allow only one thread to purge at a time
602614
static mi_atomic_guard_t purge_guard;
603615
mi_atomic_guard(&purge_guard)
604616
{
605-
mi_msecs_t now = _mi_clock_now();
606-
size_t max_purge_count = (visit_all ? max_arena : 1);
617+
// increase global expire: at most one purge per delay cycle
618+
mi_atomic_store_release(&mi_arenas_purge_expire, now + mi_arena_purge_delay());
619+
size_t max_purge_count = (visit_all ? max_arena : 2);
620+
bool all_visited = true;
607621
for (size_t i = 0; i < max_arena; i++) {
608622
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[i]);
609623
if (arena != NULL) {
610624
if (mi_arena_try_purge(arena, now, force)) {
611-
if (max_purge_count <= 1) break;
625+
if (max_purge_count <= 1) {
626+
all_visited = false;
627+
break;
628+
}
612629
max_purge_count--;
613630
}
614631
}
615632
}
633+
if (all_visited) {
634+
// all arena's were visited and purged: reset global expire
635+
mi_atomic_store_release(&mi_arenas_purge_expire, 0);
636+
}
616637
}
617638
}
618639

0 commit comments

Comments
 (0)