mirror of
https://github.com/microsoft/mimalloc.git
synced 2025-05-06 15:29:31 +03:00
merge from dev-abandon
This commit is contained in:
commit
8fb51aae4d
7 changed files with 294 additions and 327 deletions
|
@ -345,6 +345,7 @@ typedef enum mi_option_e {
|
||||||
mi_option_arena_reserve, // initial memory size in KiB for arena reservation (1GiB on 64-bit)
|
mi_option_arena_reserve, // initial memory size in KiB for arena reservation (1GiB on 64-bit)
|
||||||
mi_option_arena_purge_mult,
|
mi_option_arena_purge_mult,
|
||||||
mi_option_purge_extend_delay,
|
mi_option_purge_extend_delay,
|
||||||
|
mi_option_abandoned_reclaim_on_free, // reclaim abandoned segments on a free
|
||||||
_mi_option_last,
|
_mi_option_last,
|
||||||
// legacy option names
|
// legacy option names
|
||||||
mi_option_large_os_pages = mi_option_allow_large_os_pages,
|
mi_option_large_os_pages = mi_option_allow_large_os_pages,
|
||||||
|
|
|
@ -125,6 +125,10 @@ bool _mi_arena_contains(const void* p);
|
||||||
void _mi_arena_collect(bool force_purge, mi_stats_t* stats);
|
void _mi_arena_collect(bool force_purge, mi_stats_t* stats);
|
||||||
void _mi_arena_unsafe_destroy_all(mi_stats_t* stats);
|
void _mi_arena_unsafe_destroy_all(mi_stats_t* stats);
|
||||||
|
|
||||||
|
bool _mi_arena_segment_clear_abandoned(mi_memid_t memid);
|
||||||
|
void _mi_arena_segment_mark_abandoned(mi_memid_t memid);
|
||||||
|
mi_segment_t* _mi_arena_segment_clear_abandoned_next(mi_arena_id_t* current_id, size_t* current_idx);
|
||||||
|
|
||||||
// "segment-map.c"
|
// "segment-map.c"
|
||||||
void _mi_segment_map_allocated_at(const mi_segment_t* segment);
|
void _mi_segment_map_allocated_at(const mi_segment_t* segment);
|
||||||
void _mi_segment_map_freed_at(const mi_segment_t* segment);
|
void _mi_segment_map_freed_at(const mi_segment_t* segment);
|
||||||
|
@ -146,6 +150,7 @@ uint8_t* _mi_segment_page_start(const mi_segment_t* segment, const mi_page_t*
|
||||||
void _mi_abandoned_reclaim_all(mi_heap_t* heap, mi_segments_tld_t* tld);
|
void _mi_abandoned_reclaim_all(mi_heap_t* heap, mi_segments_tld_t* tld);
|
||||||
void _mi_abandoned_await_readers(void);
|
void _mi_abandoned_await_readers(void);
|
||||||
void _mi_abandoned_collect(mi_heap_t* heap, bool force, mi_segments_tld_t* tld);
|
void _mi_abandoned_collect(mi_heap_t* heap, bool force, mi_segments_tld_t* tld);
|
||||||
|
bool _mi_segment_attempt_reclaim(mi_heap_t* heap, mi_segment_t* segment);
|
||||||
|
|
||||||
// "page.c"
|
// "page.c"
|
||||||
void* _mi_malloc_generic(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment) mi_attr_noexcept mi_attr_malloc;
|
void* _mi_malloc_generic(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment) mi_attr_noexcept mi_attr_malloc;
|
||||||
|
|
|
@ -424,8 +424,6 @@ typedef struct mi_segment_s {
|
||||||
mi_commit_mask_t purge_mask;
|
mi_commit_mask_t purge_mask;
|
||||||
mi_commit_mask_t commit_mask;
|
mi_commit_mask_t commit_mask;
|
||||||
|
|
||||||
_Atomic(struct mi_segment_s*) abandoned_next;
|
|
||||||
|
|
||||||
// from here is zero initialized
|
// from here is zero initialized
|
||||||
struct mi_segment_s* next; // the list of freed segments in the cache (must be first field, see `segment.c:mi_segment_init`)
|
struct mi_segment_s* next; // the list of freed segments in the cache (must be first field, see `segment.c:mi_segment_init`)
|
||||||
|
|
||||||
|
|
14
src/alloc.c
14
src/alloc.c
|
@ -401,13 +401,25 @@ static void mi_stat_huge_free(const mi_page_t* page) {
|
||||||
// multi-threaded free (or free in huge block if compiled with MI_HUGE_PAGE_ABANDON)
|
// multi-threaded free (or free in huge block if compiled with MI_HUGE_PAGE_ABANDON)
|
||||||
static mi_decl_noinline void _mi_free_block_mt(mi_page_t* page, mi_block_t* block)
|
static mi_decl_noinline void _mi_free_block_mt(mi_page_t* page, mi_block_t* block)
|
||||||
{
|
{
|
||||||
|
// first see if the segment was abandoned and we can reclaim it
|
||||||
|
mi_segment_t* const segment = _mi_page_segment(page);
|
||||||
|
if (mi_option_is_enabled(mi_option_abandoned_reclaim_on_free) &&
|
||||||
|
mi_atomic_load_relaxed(&segment->thread_id) == 0)
|
||||||
|
{
|
||||||
|
// the segment is abandoned, try to reclaim it into our heap
|
||||||
|
if (_mi_segment_attempt_reclaim(mi_prim_get_default_heap(), segment)) {
|
||||||
|
mi_assert_internal(_mi_prim_thread_id() == mi_atomic_load_relaxed(&segment->thread_id));
|
||||||
|
mi_free(block); // recursively free as now it will be a local free in our heap
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The padding check may access the non-thread-owned page for the key values.
|
// The padding check may access the non-thread-owned page for the key values.
|
||||||
// that is safe as these are constant and the page won't be freed (as the block is not freed yet).
|
// that is safe as these are constant and the page won't be freed (as the block is not freed yet).
|
||||||
mi_check_padding(page, block);
|
mi_check_padding(page, block);
|
||||||
_mi_padding_shrink(page, block, sizeof(mi_block_t)); // for small size, ensure we can fit the delayed thread pointers without triggering overflow detection
|
_mi_padding_shrink(page, block, sizeof(mi_block_t)); // for small size, ensure we can fit the delayed thread pointers without triggering overflow detection
|
||||||
|
|
||||||
// huge page segments are always abandoned and can be freed immediately
|
// huge page segments are always abandoned and can be freed immediately
|
||||||
mi_segment_t* segment = _mi_page_segment(page);
|
|
||||||
if (segment->kind == MI_SEGMENT_HUGE) {
|
if (segment->kind == MI_SEGMENT_HUGE) {
|
||||||
#if MI_HUGE_PAGE_ABANDON
|
#if MI_HUGE_PAGE_ABANDON
|
||||||
// huge page segments are always abandoned and can be freed immediately
|
// huge page segments are always abandoned and can be freed immediately
|
||||||
|
|
97
src/arena.c
97
src/arena.c
|
@ -55,6 +55,7 @@ typedef struct mi_arena_s {
|
||||||
mi_bitmap_field_t* blocks_dirty; // are the blocks potentially non-zero?
|
mi_bitmap_field_t* blocks_dirty; // are the blocks potentially non-zero?
|
||||||
mi_bitmap_field_t* blocks_committed; // are the blocks committed? (can be NULL for memory that cannot be decommitted)
|
mi_bitmap_field_t* blocks_committed; // are the blocks committed? (can be NULL for memory that cannot be decommitted)
|
||||||
mi_bitmap_field_t* blocks_purge; // blocks that can be (reset) decommitted. (can be NULL for memory that cannot be (reset) decommitted)
|
mi_bitmap_field_t* blocks_purge; // blocks that can be (reset) decommitted. (can be NULL for memory that cannot be (reset) decommitted)
|
||||||
|
mi_bitmap_field_t* blocks_abandoned; // blocks that start with an abandoned segment. (This crosses API's but it is convenient to have here)
|
||||||
mi_bitmap_field_t blocks_inuse[1]; // in-place bitmap of in-use blocks (of size `field_count`)
|
mi_bitmap_field_t blocks_inuse[1]; // in-place bitmap of in-use blocks (of size `field_count`)
|
||||||
} mi_arena_t;
|
} mi_arena_t;
|
||||||
|
|
||||||
|
@ -94,7 +95,7 @@ bool _mi_arena_memid_is_suitable(mi_memid_t memid, mi_arena_id_t request_arena_i
|
||||||
return mi_arena_id_is_suitable(memid.mem.arena.id, memid.mem.arena.is_exclusive, request_arena_id);
|
return mi_arena_id_is_suitable(memid.mem.arena.id, memid.mem.arena.is_exclusive, request_arena_id);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return mi_arena_id_is_suitable(0, false, request_arena_id);
|
return mi_arena_id_is_suitable(_mi_arena_id_none(), false, request_arena_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -730,6 +731,90 @@ bool _mi_arena_contains(const void* p) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -----------------------------------------------------------
|
||||||
|
Abandoned blocks/segments.
|
||||||
|
This is used to atomically abandon/reclaim segments
|
||||||
|
(and crosses the arena API but it is convenient to have here).
|
||||||
|
Abandoned segments still have live blocks; they get reclaimed
|
||||||
|
when a thread frees in it, or when a thread needs a fresh
|
||||||
|
segment; these threads scan the abandoned segments through
|
||||||
|
the arena bitmaps.
|
||||||
|
----------------------------------------------------------- */
|
||||||
|
|
||||||
|
// reclaim a specific abandoned segment; `true` on success.
|
||||||
|
bool _mi_arena_segment_clear_abandoned(mi_memid_t memid )
|
||||||
|
{
|
||||||
|
if (memid.memkind != MI_MEM_ARENA) return true; // not in an arena, consider it un-abandoned
|
||||||
|
size_t arena_idx;
|
||||||
|
size_t bitmap_idx;
|
||||||
|
mi_arena_memid_indices(memid, &arena_idx, &bitmap_idx);
|
||||||
|
mi_assert_internal(arena_idx < MI_MAX_ARENAS);
|
||||||
|
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[arena_idx]);
|
||||||
|
mi_assert_internal(arena != NULL);
|
||||||
|
bool was_abandoned = _mi_bitmap_unclaim(arena->blocks_abandoned, arena->field_count, 1, bitmap_idx);
|
||||||
|
// mi_assert_internal(was_abandoned);
|
||||||
|
mi_assert_internal(!was_abandoned || _mi_bitmap_is_claimed(arena->blocks_inuse, arena->field_count, 1, bitmap_idx));
|
||||||
|
//mi_assert_internal(arena->blocks_committed == NULL || _mi_bitmap_is_claimed(arena->blocks_committed, arena->field_count, 1, bitmap_idx));
|
||||||
|
return was_abandoned;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark a specific segment as abandoned
|
||||||
|
void _mi_arena_segment_mark_abandoned(mi_memid_t memid)
|
||||||
|
{
|
||||||
|
if (memid.memkind != MI_MEM_ARENA) return; // not in an arena
|
||||||
|
size_t arena_idx;
|
||||||
|
size_t bitmap_idx;
|
||||||
|
mi_arena_memid_indices(memid, &arena_idx, &bitmap_idx);
|
||||||
|
mi_assert_internal(arena_idx < MI_MAX_ARENAS);
|
||||||
|
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[arena_idx]);
|
||||||
|
mi_assert_internal(arena != NULL);
|
||||||
|
const bool was_unset = _mi_bitmap_claim(arena->blocks_abandoned, arena->field_count, 1, bitmap_idx, NULL);
|
||||||
|
MI_UNUSED_RELEASE(was_unset);
|
||||||
|
mi_assert_internal(was_unset);
|
||||||
|
mi_assert_internal(_mi_bitmap_is_claimed(arena->blocks_inuse, arena->field_count, 1, bitmap_idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
// reclaim abandoned segments
|
||||||
|
mi_segment_t* _mi_arena_segment_clear_abandoned_next(mi_arena_id_t* previous_id, size_t* previous_idx )
|
||||||
|
{
|
||||||
|
const int max_arena = (int)mi_atomic_load_relaxed(&mi_arena_count);
|
||||||
|
int arena_idx = *previous_id;
|
||||||
|
size_t field_idx = mi_bitmap_index_field(*previous_idx);
|
||||||
|
size_t bit_idx = mi_bitmap_index_bit_in_field(*previous_idx) + 1;
|
||||||
|
// visit arena's (from previous)
|
||||||
|
for( ; arena_idx < max_arena; arena_idx++, field_idx = 0, bit_idx = 0) {
|
||||||
|
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[arena_idx]);
|
||||||
|
if (arena != NULL) {
|
||||||
|
// visit the abandoned fields (starting at previous_idx)
|
||||||
|
for ( ; field_idx < arena->field_count; field_idx++, bit_idx = 0) {
|
||||||
|
mi_bitmap_field_t field = mi_atomic_load_relaxed(&arena->blocks_abandoned[field_idx]);
|
||||||
|
if mi_unlikely(field != 0) { // skip zero fields quickly
|
||||||
|
// visit each set bit in the field (todo: maybe use `ctz` here?)
|
||||||
|
for ( ; bit_idx < MI_BITMAP_FIELD_BITS; bit_idx++) {
|
||||||
|
// pre-check if the bit is set
|
||||||
|
mi_bitmap_field_t mask = ((mi_bitmap_field_t)1 << bit_idx);
|
||||||
|
if mi_unlikely((field & mask) == mask) {
|
||||||
|
mi_bitmap_index_t bitmap_idx = mi_bitmap_index_create(field_idx, bit_idx);
|
||||||
|
// try to reclaim it atomically
|
||||||
|
if (_mi_bitmap_unclaim(arena->blocks_abandoned, arena->field_count, 1, bitmap_idx)) {
|
||||||
|
*previous_idx = bitmap_idx;
|
||||||
|
*previous_id = arena_idx;
|
||||||
|
mi_assert_internal(_mi_bitmap_is_claimed(arena->blocks_inuse, arena->field_count, 1, bitmap_idx));
|
||||||
|
//mi_assert_internal(arena->blocks_committed == NULL || _mi_bitmap_is_claimed(arena->blocks_committed, arena->field_count, 1, bitmap_idx));
|
||||||
|
return (mi_segment_t*)mi_arena_block_start(arena, bitmap_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no more found
|
||||||
|
*previous_idx = 0;
|
||||||
|
*previous_id = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -----------------------------------------------------------
|
/* -----------------------------------------------------------
|
||||||
Add an arena.
|
Add an arena.
|
||||||
|
@ -763,13 +848,13 @@ static bool mi_manage_os_memory_ex2(void* start, size_t size, bool is_large, int
|
||||||
|
|
||||||
const size_t bcount = size / MI_ARENA_BLOCK_SIZE;
|
const size_t bcount = size / MI_ARENA_BLOCK_SIZE;
|
||||||
const size_t fields = _mi_divide_up(bcount, MI_BITMAP_FIELD_BITS);
|
const size_t fields = _mi_divide_up(bcount, MI_BITMAP_FIELD_BITS);
|
||||||
const size_t bitmaps = (memid.is_pinned ? 2 : 4);
|
const size_t bitmaps = (memid.is_pinned ? 3 : 5);
|
||||||
const size_t asize = sizeof(mi_arena_t) + (bitmaps*fields*sizeof(mi_bitmap_field_t));
|
const size_t asize = sizeof(mi_arena_t) + (bitmaps*fields*sizeof(mi_bitmap_field_t));
|
||||||
mi_memid_t meta_memid;
|
mi_memid_t meta_memid;
|
||||||
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?
|
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?
|
||||||
if (arena == NULL) return false;
|
if (arena == NULL) return false;
|
||||||
|
|
||||||
// already zero'd due to os_alloc
|
// already zero'd due to zalloc
|
||||||
// _mi_memzero(arena, asize);
|
// _mi_memzero(arena, asize);
|
||||||
arena->id = _mi_arena_id_none();
|
arena->id = _mi_arena_id_none();
|
||||||
arena->memid = memid;
|
arena->memid = memid;
|
||||||
|
@ -783,9 +868,11 @@ static bool mi_manage_os_memory_ex2(void* start, size_t size, bool is_large, int
|
||||||
arena->is_large = is_large;
|
arena->is_large = is_large;
|
||||||
arena->purge_expire = 0;
|
arena->purge_expire = 0;
|
||||||
arena->search_idx = 0;
|
arena->search_idx = 0;
|
||||||
|
// consequetive bitmaps
|
||||||
arena->blocks_dirty = &arena->blocks_inuse[fields]; // just after inuse bitmap
|
arena->blocks_dirty = &arena->blocks_inuse[fields]; // just after inuse bitmap
|
||||||
arena->blocks_committed = (arena->memid.is_pinned ? NULL : &arena->blocks_inuse[2*fields]); // just after dirty bitmap
|
arena->blocks_abandoned = &arena->blocks_inuse[2 * fields]; // just after dirty bitmap
|
||||||
arena->blocks_purge = (arena->memid.is_pinned ? NULL : &arena->blocks_inuse[3*fields]); // just after committed bitmap
|
arena->blocks_committed = (arena->memid.is_pinned ? NULL : &arena->blocks_inuse[3*fields]); // just after abandonde bitmap
|
||||||
|
arena->blocks_purge = (arena->memid.is_pinned ? NULL : &arena->blocks_inuse[4*fields]); // just after committed bitmap
|
||||||
// initialize committed bitmap?
|
// initialize committed bitmap?
|
||||||
if (arena->blocks_committed != NULL && arena->memid.initially_committed) {
|
if (arena->blocks_committed != NULL && arena->memid.initially_committed) {
|
||||||
memset((void*)arena->blocks_committed, 0xFF, fields*sizeof(mi_bitmap_field_t)); // cast to void* to avoid atomic warning
|
memset((void*)arena->blocks_committed, 0xFF, fields*sizeof(mi_bitmap_field_t)); // cast to void* to avoid atomic warning
|
||||||
|
|
|
@ -81,7 +81,7 @@ static mi_option_desc_t options[_mi_option_last] =
|
||||||
{ 100, UNINIT, MI_OPTION(os_tag) }, // only apple specific for now but might serve more or less related purpose
|
{ 100, UNINIT, MI_OPTION(os_tag) }, // only apple specific for now but might serve more or less related purpose
|
||||||
{ 16, UNINIT, MI_OPTION(max_errors) }, // maximum errors that are output
|
{ 16, UNINIT, MI_OPTION(max_errors) }, // maximum errors that are output
|
||||||
{ 16, UNINIT, MI_OPTION(max_warnings) }, // maximum warnings that are output
|
{ 16, UNINIT, MI_OPTION(max_warnings) }, // maximum warnings that are output
|
||||||
{ 8, UNINIT, MI_OPTION(max_segment_reclaim)}, // max. number of segment reclaims from the abandoned segments per try.
|
{ 16, UNINIT, MI_OPTION(max_segment_reclaim)}, // max. number of segment reclaims from the abandoned segments per try.
|
||||||
{ 0, UNINIT, MI_OPTION(destroy_on_exit)}, // release all OS memory on process exit; careful with dangling pointer or after-exit frees!
|
{ 0, UNINIT, MI_OPTION(destroy_on_exit)}, // release all OS memory on process exit; careful with dangling pointer or after-exit frees!
|
||||||
#if (MI_INTPTR_SIZE>4)
|
#if (MI_INTPTR_SIZE>4)
|
||||||
{ 1024L * 1024L, UNINIT, MI_OPTION(arena_reserve) }, // reserve memory N KiB at a time
|
{ 1024L * 1024L, UNINIT, MI_OPTION(arena_reserve) }, // reserve memory N KiB at a time
|
||||||
|
@ -90,6 +90,7 @@ static mi_option_desc_t options[_mi_option_last] =
|
||||||
#endif
|
#endif
|
||||||
{ 10, UNINIT, MI_OPTION(arena_purge_mult) }, // purge delay multiplier for arena's
|
{ 10, UNINIT, MI_OPTION(arena_purge_mult) }, // purge delay multiplier for arena's
|
||||||
{ 1, UNINIT, MI_OPTION_LEGACY(purge_extend_delay, decommit_extend_delay) },
|
{ 1, UNINIT, MI_OPTION_LEGACY(purge_extend_delay, decommit_extend_delay) },
|
||||||
|
{ 1, UNINIT, MI_OPTION(abandoned_reclaim_on_free) }, // reclaim an abandoned segment on a free
|
||||||
};
|
};
|
||||||
|
|
||||||
static void mi_option_init(mi_option_desc_t* desc);
|
static void mi_option_init(mi_option_desc_t* desc);
|
||||||
|
|
213
src/segment.c
213
src/segment.c
|
@ -847,7 +847,6 @@ static mi_segment_t* mi_segment_os_alloc( size_t required, size_t page_alignment
|
||||||
segment->commit_mask = commit_mask;
|
segment->commit_mask = commit_mask;
|
||||||
segment->purge_expire = 0;
|
segment->purge_expire = 0;
|
||||||
mi_commit_mask_create_empty(&segment->purge_mask);
|
mi_commit_mask_create_empty(&segment->purge_mask);
|
||||||
mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, NULL); // tsan
|
|
||||||
|
|
||||||
mi_segments_track_size((long)(segment_size), tld);
|
mi_segments_track_size((long)(segment_size), tld);
|
||||||
_mi_segment_map_allocated_at(segment);
|
_mi_segment_map_allocated_at(segment);
|
||||||
|
@ -1036,172 +1035,20 @@ Abandonment
|
||||||
When threads terminate, they can leave segments with
|
When threads terminate, they can leave segments with
|
||||||
live blocks (reachable through other threads). Such segments
|
live blocks (reachable through other threads). Such segments
|
||||||
are "abandoned" and will be reclaimed by other threads to
|
are "abandoned" and will be reclaimed by other threads to
|
||||||
reuse their pages and/or free them eventually
|
reuse their pages and/or free them eventually. The
|
||||||
|
`thread_id` of such segments is 0.
|
||||||
|
|
||||||
We maintain a global list of abandoned segments that are
|
When a block is freed in an abandoned segment, the segment
|
||||||
reclaimed on demand. Since this is shared among threads
|
is reclaimed into that thread.
|
||||||
the implementation needs to avoid the A-B-A problem on
|
|
||||||
popping abandoned segments: <https://en.wikipedia.org/wiki/ABA_problem>
|
|
||||||
We use tagged pointers to avoid accidentally identifying
|
|
||||||
reused segments, much like stamped references in Java.
|
|
||||||
Secondly, we maintain a reader counter to avoid resetting
|
|
||||||
or decommitting segments that have a pending read operation.
|
|
||||||
|
|
||||||
Note: the current implementation is one possible design;
|
|
||||||
another way might be to keep track of abandoned segments
|
|
||||||
in the arenas/segment_cache's. This would have the advantage of keeping
|
|
||||||
all concurrent code in one place and not needing to deal
|
|
||||||
with ABA issues. The drawback is that it is unclear how to
|
|
||||||
scan abandoned segments efficiently in that case as they
|
|
||||||
would be spread among all other segments in the arenas.
|
|
||||||
----------------------------------------------------------- */
|
----------------------------------------------------------- */
|
||||||
|
|
||||||
// Use the bottom 20-bits (on 64-bit) of the aligned segment pointers
|
// Maintain these for debug purposes
|
||||||
// to put in a tag that increments on update to avoid the A-B-A problem.
|
static mi_decl_cache_align _Atomic(size_t)abandoned_count;
|
||||||
#define MI_TAGGED_MASK MI_SEGMENT_MASK
|
|
||||||
typedef uintptr_t mi_tagged_segment_t;
|
|
||||||
|
|
||||||
static mi_segment_t* mi_tagged_segment_ptr(mi_tagged_segment_t ts) {
|
|
||||||
return (mi_segment_t*)(ts & ~MI_TAGGED_MASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
static mi_tagged_segment_t mi_tagged_segment(mi_segment_t* segment, mi_tagged_segment_t ts) {
|
// legacy: Wait until there are no more pending reads on segments that used to be in the abandoned list
|
||||||
mi_assert_internal(((uintptr_t)segment & MI_TAGGED_MASK) == 0);
|
|
||||||
uintptr_t tag = ((ts & MI_TAGGED_MASK) + 1) & MI_TAGGED_MASK;
|
|
||||||
return ((uintptr_t)segment | tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a list of visited abandoned pages that were full at the time.
|
|
||||||
// this list migrates to `abandoned` when that becomes NULL. The use of
|
|
||||||
// this list reduces contention and the rate at which segments are visited.
|
|
||||||
static mi_decl_cache_align _Atomic(mi_segment_t*) abandoned_visited; // = NULL
|
|
||||||
|
|
||||||
// The abandoned page list (tagged as it supports pop)
|
|
||||||
static mi_decl_cache_align _Atomic(mi_tagged_segment_t) abandoned; // = NULL
|
|
||||||
|
|
||||||
// Maintain these for debug purposes (these counts may be a bit off)
|
|
||||||
static mi_decl_cache_align _Atomic(size_t) abandoned_count;
|
|
||||||
static mi_decl_cache_align _Atomic(size_t) abandoned_visited_count;
|
|
||||||
|
|
||||||
// We also maintain a count of current readers of the abandoned list
|
|
||||||
// in order to prevent resetting/decommitting segment memory if it might
|
|
||||||
// still be read.
|
|
||||||
static mi_decl_cache_align _Atomic(size_t) abandoned_readers; // = 0
|
|
||||||
|
|
||||||
// Push on the visited list
|
|
||||||
static void mi_abandoned_visited_push(mi_segment_t* segment) {
|
|
||||||
mi_assert_internal(segment->thread_id == 0);
|
|
||||||
mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_segment_t,&segment->abandoned_next) == NULL);
|
|
||||||
mi_assert_internal(segment->next == NULL);
|
|
||||||
mi_assert_internal(segment->used > 0);
|
|
||||||
mi_segment_t* anext = mi_atomic_load_ptr_relaxed(mi_segment_t, &abandoned_visited);
|
|
||||||
do {
|
|
||||||
mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, anext);
|
|
||||||
} while (!mi_atomic_cas_ptr_weak_release(mi_segment_t, &abandoned_visited, &anext, segment));
|
|
||||||
mi_atomic_increment_relaxed(&abandoned_visited_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the visited list to the abandoned list.
|
|
||||||
static bool mi_abandoned_visited_revisit(void)
|
|
||||||
{
|
|
||||||
// quick check if the visited list is empty
|
|
||||||
if (mi_atomic_load_ptr_relaxed(mi_segment_t, &abandoned_visited) == NULL) return false;
|
|
||||||
|
|
||||||
// grab the whole visited list
|
|
||||||
mi_segment_t* first = mi_atomic_exchange_ptr_acq_rel(mi_segment_t, &abandoned_visited, NULL);
|
|
||||||
if (first == NULL) return false;
|
|
||||||
|
|
||||||
// first try to swap directly if the abandoned list happens to be NULL
|
|
||||||
mi_tagged_segment_t afirst;
|
|
||||||
mi_tagged_segment_t ts = mi_atomic_load_relaxed(&abandoned);
|
|
||||||
if (mi_tagged_segment_ptr(ts)==NULL) {
|
|
||||||
size_t count = mi_atomic_load_relaxed(&abandoned_visited_count);
|
|
||||||
afirst = mi_tagged_segment(first, ts);
|
|
||||||
if (mi_atomic_cas_strong_acq_rel(&abandoned, &ts, afirst)) {
|
|
||||||
mi_atomic_add_relaxed(&abandoned_count, count);
|
|
||||||
mi_atomic_sub_relaxed(&abandoned_visited_count, count);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// find the last element of the visited list: O(n)
|
|
||||||
mi_segment_t* last = first;
|
|
||||||
mi_segment_t* next;
|
|
||||||
while ((next = mi_atomic_load_ptr_relaxed(mi_segment_t, &last->abandoned_next)) != NULL) {
|
|
||||||
last = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
// and atomically prepend to the abandoned list
|
|
||||||
// (no need to increase the readers as we don't access the abandoned segments)
|
|
||||||
mi_tagged_segment_t anext = mi_atomic_load_relaxed(&abandoned);
|
|
||||||
size_t count;
|
|
||||||
do {
|
|
||||||
count = mi_atomic_load_relaxed(&abandoned_visited_count);
|
|
||||||
mi_atomic_store_ptr_release(mi_segment_t, &last->abandoned_next, mi_tagged_segment_ptr(anext));
|
|
||||||
afirst = mi_tagged_segment(first, anext);
|
|
||||||
} while (!mi_atomic_cas_weak_release(&abandoned, &anext, afirst));
|
|
||||||
mi_atomic_add_relaxed(&abandoned_count, count);
|
|
||||||
mi_atomic_sub_relaxed(&abandoned_visited_count, count);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push on the abandoned list.
|
|
||||||
static void mi_abandoned_push(mi_segment_t* segment) {
|
|
||||||
mi_assert_internal(segment->thread_id == 0);
|
|
||||||
mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_segment_t, &segment->abandoned_next) == NULL);
|
|
||||||
mi_assert_internal(segment->next == NULL);
|
|
||||||
mi_assert_internal(segment->used > 0);
|
|
||||||
mi_tagged_segment_t next;
|
|
||||||
mi_tagged_segment_t ts = mi_atomic_load_relaxed(&abandoned);
|
|
||||||
do {
|
|
||||||
mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, mi_tagged_segment_ptr(ts));
|
|
||||||
next = mi_tagged_segment(segment, ts);
|
|
||||||
} while (!mi_atomic_cas_weak_release(&abandoned, &ts, next));
|
|
||||||
mi_atomic_increment_relaxed(&abandoned_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until there are no more pending reads on segments that used to be in the abandoned list
|
|
||||||
// called for example from `arena.c` before decommitting
|
|
||||||
void _mi_abandoned_await_readers(void) {
|
void _mi_abandoned_await_readers(void) {
|
||||||
size_t n;
|
// nothing needed
|
||||||
do {
|
|
||||||
n = mi_atomic_load_acquire(&abandoned_readers);
|
|
||||||
if (n != 0) mi_atomic_yield();
|
|
||||||
} while (n != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pop from the abandoned list
|
|
||||||
static mi_segment_t* mi_abandoned_pop(void) {
|
|
||||||
mi_segment_t* segment;
|
|
||||||
// Check efficiently if it is empty (or if the visited list needs to be moved)
|
|
||||||
mi_tagged_segment_t ts = mi_atomic_load_relaxed(&abandoned);
|
|
||||||
segment = mi_tagged_segment_ptr(ts);
|
|
||||||
if mi_likely(segment == NULL) {
|
|
||||||
if mi_likely(!mi_abandoned_visited_revisit()) { // try to swap in the visited list on NULL
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do a pop. We use a reader count to prevent
|
|
||||||
// a segment to be decommitted while a read is still pending,
|
|
||||||
// and a tagged pointer to prevent A-B-A link corruption.
|
|
||||||
// (this is called from `region.c:_mi_mem_free` for example)
|
|
||||||
mi_atomic_increment_relaxed(&abandoned_readers); // ensure no segment gets decommitted
|
|
||||||
mi_tagged_segment_t next = 0;
|
|
||||||
ts = mi_atomic_load_acquire(&abandoned);
|
|
||||||
do {
|
|
||||||
segment = mi_tagged_segment_ptr(ts);
|
|
||||||
if (segment != NULL) {
|
|
||||||
mi_segment_t* anext = mi_atomic_load_ptr_relaxed(mi_segment_t, &segment->abandoned_next);
|
|
||||||
next = mi_tagged_segment(anext, ts); // note: reads the segment's `abandoned_next` field so should not be decommitted
|
|
||||||
}
|
|
||||||
} while (segment != NULL && !mi_atomic_cas_weak_acq_rel(&abandoned, &ts, next));
|
|
||||||
mi_atomic_decrement_relaxed(&abandoned_readers); // release reader lock
|
|
||||||
if (segment != NULL) {
|
|
||||||
mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, NULL);
|
|
||||||
mi_atomic_decrement_relaxed(&abandoned_count);
|
|
||||||
}
|
|
||||||
return segment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -----------------------------------------------------------
|
/* -----------------------------------------------------------
|
||||||
|
@ -1211,7 +1058,6 @@ static mi_segment_t* mi_abandoned_pop(void) {
|
||||||
static void mi_segment_abandon(mi_segment_t* segment, mi_segments_tld_t* tld) {
|
static void mi_segment_abandon(mi_segment_t* segment, mi_segments_tld_t* tld) {
|
||||||
mi_assert_internal(segment->used == segment->abandoned);
|
mi_assert_internal(segment->used == segment->abandoned);
|
||||||
mi_assert_internal(segment->used > 0);
|
mi_assert_internal(segment->used > 0);
|
||||||
mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_segment_t, &segment->abandoned_next) == NULL);
|
|
||||||
mi_assert_internal(segment->abandoned_visits == 0);
|
mi_assert_internal(segment->abandoned_visits == 0);
|
||||||
mi_assert_expensive(mi_segment_is_valid(segment,tld));
|
mi_assert_expensive(mi_segment_is_valid(segment,tld));
|
||||||
|
|
||||||
|
@ -1235,9 +1081,8 @@ static void mi_segment_abandon(mi_segment_t* segment, mi_segments_tld_t* tld) {
|
||||||
_mi_stat_increase(&tld->stats->segments_abandoned, 1);
|
_mi_stat_increase(&tld->stats->segments_abandoned, 1);
|
||||||
mi_segments_track_size(-((long)mi_segment_size(segment)), tld);
|
mi_segments_track_size(-((long)mi_segment_size(segment)), tld);
|
||||||
segment->thread_id = 0;
|
segment->thread_id = 0;
|
||||||
mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, NULL);
|
|
||||||
segment->abandoned_visits = 1; // from 0 to 1 to signify it is abandoned
|
segment->abandoned_visits = 1; // from 0 to 1 to signify it is abandoned
|
||||||
mi_abandoned_push(segment);
|
_mi_arena_segment_mark_abandoned(segment->memid); mi_atomic_increment_relaxed(&abandoned_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _mi_segment_page_abandon(mi_page_t* page, mi_segments_tld_t* tld) {
|
void _mi_segment_page_abandon(mi_page_t* page, mi_segments_tld_t* tld) {
|
||||||
|
@ -1318,7 +1163,6 @@ static bool mi_segment_check_free(mi_segment_t* segment, size_t slices_needed, s
|
||||||
// Reclaim an abandoned segment; returns NULL if the segment was freed
|
// Reclaim an abandoned segment; returns NULL if the segment was freed
|
||||||
// set `right_page_reclaimed` to `true` if it reclaimed a page of the right `block_size` that was not full.
|
// set `right_page_reclaimed` to `true` if it reclaimed a page of the right `block_size` that was not full.
|
||||||
static mi_segment_t* mi_segment_reclaim(mi_segment_t* segment, mi_heap_t* heap, size_t requested_block_size, bool* right_page_reclaimed, mi_segments_tld_t* tld) {
|
static mi_segment_t* mi_segment_reclaim(mi_segment_t* segment, mi_heap_t* heap, size_t requested_block_size, bool* right_page_reclaimed, mi_segments_tld_t* tld) {
|
||||||
mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_segment_t, &segment->abandoned_next) == NULL);
|
|
||||||
mi_assert_expensive(mi_segment_is_valid(segment, tld));
|
mi_assert_expensive(mi_segment_is_valid(segment, tld));
|
||||||
if (right_page_reclaimed != NULL) { *right_page_reclaimed = false; }
|
if (right_page_reclaimed != NULL) { *right_page_reclaimed = false; }
|
||||||
|
|
||||||
|
@ -1378,10 +1222,23 @@ static mi_segment_t* mi_segment_reclaim(mi_segment_t* segment, mi_heap_t* heap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// attempt to reclaim a particular segment (called from multi threaded free `alloc.c:mi_free_block_mt`)
|
||||||
|
bool _mi_segment_attempt_reclaim(mi_heap_t* heap, mi_segment_t* segment) {
|
||||||
|
if (mi_atomic_load_relaxed(&segment->thread_id) != 0) return false; // it is not abandoned
|
||||||
|
if (_mi_arena_segment_clear_abandoned(segment->memid)) { // atomically unabandon
|
||||||
|
mi_segment_t* res = mi_segment_reclaim(segment, heap, 0, NULL, &heap->tld->segments);
|
||||||
|
mi_assert_internal(res != NULL);
|
||||||
|
return (res != NULL);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void _mi_abandoned_reclaim_all(mi_heap_t* heap, mi_segments_tld_t* tld) {
|
void _mi_abandoned_reclaim_all(mi_heap_t* heap, mi_segments_tld_t* tld) {
|
||||||
mi_segment_t* segment;
|
mi_segment_t* segment;
|
||||||
while ((segment = mi_abandoned_pop()) != NULL) {
|
mi_arena_id_t current_id = 0;
|
||||||
|
size_t current_idx = 0;
|
||||||
|
while ((segment = _mi_arena_segment_clear_abandoned_next(¤t_id, ¤t_idx)) != NULL) {
|
||||||
|
mi_atomic_decrement_relaxed(&abandoned_count);
|
||||||
mi_segment_reclaim(segment, heap, 0, NULL, tld);
|
mi_segment_reclaim(segment, heap, 0, NULL, tld);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1390,8 +1247,12 @@ static mi_segment_t* mi_segment_try_reclaim(mi_heap_t* heap, size_t needed_slice
|
||||||
{
|
{
|
||||||
*reclaimed = false;
|
*reclaimed = false;
|
||||||
mi_segment_t* segment;
|
mi_segment_t* segment;
|
||||||
long max_tries = mi_option_get_clamp(mi_option_max_segment_reclaim, 8, 1024); // limit the work to bound allocation times
|
mi_arena_id_t current_id = 0;
|
||||||
while ((max_tries-- > 0) && ((segment = mi_abandoned_pop()) != NULL)) {
|
size_t current_idx = 0;
|
||||||
|
long max_tries = mi_option_get_clamp(mi_option_max_segment_reclaim, 0, 1024); // limit the work to bound allocation times
|
||||||
|
while ((max_tries-- > 0) && ((segment = _mi_arena_segment_clear_abandoned_next(¤t_id, ¤t_idx)) != NULL))
|
||||||
|
{
|
||||||
|
mi_atomic_decrement_relaxed(&abandoned_count);
|
||||||
segment->abandoned_visits++;
|
segment->abandoned_visits++;
|
||||||
// todo: an arena exclusive heap will potentially visit many abandoned unsuitable segments
|
// todo: an arena exclusive heap will potentially visit many abandoned unsuitable segments
|
||||||
// and push them into the visited list and use many tries. Perhaps we can skip non-suitable ones in a better way?
|
// and push them into the visited list and use many tries. Perhaps we can skip non-suitable ones in a better way?
|
||||||
|
@ -1417,8 +1278,9 @@ static mi_segment_t* mi_segment_try_reclaim(mi_heap_t* heap, size_t needed_slice
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// otherwise, push on the visited list so it gets not looked at too quickly again
|
// otherwise, push on the visited list so it gets not looked at too quickly again
|
||||||
mi_segment_try_purge(segment, true /* force? */, tld->stats); // force purge if needed as we may not visit soon again
|
mi_segment_try_purge(segment, false /* true force? */, tld->stats); // force purge if needed as we may not visit soon again
|
||||||
mi_abandoned_visited_push(segment);
|
mi_atomic_increment_relaxed(&abandoned_count);
|
||||||
|
_mi_arena_segment_mark_abandoned(segment->memid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -1428,11 +1290,11 @@ static mi_segment_t* mi_segment_try_reclaim(mi_heap_t* heap, size_t needed_slice
|
||||||
void _mi_abandoned_collect(mi_heap_t* heap, bool force, mi_segments_tld_t* tld)
|
void _mi_abandoned_collect(mi_heap_t* heap, bool force, mi_segments_tld_t* tld)
|
||||||
{
|
{
|
||||||
mi_segment_t* segment;
|
mi_segment_t* segment;
|
||||||
|
mi_arena_id_t current_id = 0;
|
||||||
|
size_t current_idx = 0;
|
||||||
int max_tries = (force ? 16*1024 : 1024); // limit latency
|
int max_tries = (force ? 16*1024 : 1024); // limit latency
|
||||||
if (force) {
|
while ((max_tries-- > 0) && ((segment = _mi_arena_segment_clear_abandoned_next(¤t_id,¤t_idx)) != NULL)) {
|
||||||
mi_abandoned_visited_revisit();
|
mi_atomic_decrement_relaxed(&abandoned_count);
|
||||||
}
|
|
||||||
while ((max_tries-- > 0) && ((segment = mi_abandoned_pop()) != NULL)) {
|
|
||||||
mi_segment_check_free(segment,0,0,tld); // try to free up pages (due to concurrent frees)
|
mi_segment_check_free(segment,0,0,tld); // try to free up pages (due to concurrent frees)
|
||||||
if (segment->used == 0) {
|
if (segment->used == 0) {
|
||||||
// free the segment (by forced reclaim) to make it available to other threads.
|
// free the segment (by forced reclaim) to make it available to other threads.
|
||||||
|
@ -1444,7 +1306,8 @@ void _mi_abandoned_collect(mi_heap_t* heap, bool force, mi_segments_tld_t* tld)
|
||||||
// otherwise, purge if needed and push on the visited list
|
// otherwise, purge if needed and push on the visited list
|
||||||
// note: forced purge can be expensive if many threads are destroyed/created as in mstress.
|
// note: forced purge can be expensive if many threads are destroyed/created as in mstress.
|
||||||
mi_segment_try_purge(segment, force, tld->stats);
|
mi_segment_try_purge(segment, force, tld->stats);
|
||||||
mi_abandoned_visited_push(segment);
|
mi_atomic_increment_relaxed(&abandoned_count);
|
||||||
|
_mi_arena_segment_mark_abandoned(segment->memid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue