diff --git a/ide/vs2022/mimalloc-override-dll.vcxproj b/ide/vs2022/mimalloc-override-dll.vcxproj index 133b3efa..8af43a88 100644 --- a/ide/vs2022/mimalloc-override-dll.vcxproj +++ b/ide/vs2022/mimalloc-override-dll.vcxproj @@ -427,6 +427,7 @@ + diff --git a/ide/vs2022/mimalloc-override-dll.vcxproj.filters b/ide/vs2022/mimalloc-override-dll.vcxproj.filters index 00d677d3..a63eef84 100644 --- a/ide/vs2022/mimalloc-override-dll.vcxproj.filters +++ b/ide/vs2022/mimalloc-override-dll.vcxproj.filters @@ -96,6 +96,9 @@ Headers + + Headers + diff --git a/include/mimalloc-stats.h b/include/mimalloc-stats.h index 7c1ed770..44c4886f 100644 --- a/include/mimalloc-stats.h +++ b/include/mimalloc-stats.h @@ -49,6 +49,7 @@ typedef struct mi_stat_counter_s { \ /* internal statistics */ \ MI_STAT_COUNTER(arena_rollback_count) \ + MI_STAT_COUNTER(arena_purges) \ MI_STAT_COUNTER(pages_extended) /* number of page extensions */ \ MI_STAT_COUNTER(pages_retire) /* number of pages that are retired */ \ MI_STAT_COUNTER(page_searches) /* searches for a fresh page */ \ @@ -99,4 +100,4 @@ mi_decl_export char* mi_stats_get_json( size_t buf_size, char* buf ) mi_attr_noe } #endif -#endif // MIMALLOC_STATS_H \ No newline at end of file +#endif // MIMALLOC_STATS_H diff --git a/include/mimalloc.h b/include/mimalloc.h index 05d50a86..ae6ae262 100644 --- a/include/mimalloc.h +++ b/include/mimalloc.h @@ -311,7 +311,7 @@ mi_decl_export void mi_heap_guarded_set_sample_rate(mi_heap_t* heap, size_t samp mi_decl_export void mi_heap_guarded_set_size_bound(mi_heap_t* heap, size_t min, size_t max); // Experimental: communicate that the thread is part of a threadpool -mi_decl_export void mi_thread_set_in_threadpool(void) mi_attr_noexcept; +mi_decl_export void mi_thread_set_in_threadpool(void) mi_attr_noexcept; // Experimental: create a new heap with a specified heap tag. Set `allow_destroy` to false to allow the thread // to reclaim abandoned memory (with a compatible heap_tag and arena_id) but in that case `mi_heap_destroy` will @@ -386,6 +386,7 @@ typedef enum mi_option_e { mi_option_guarded_sample_rate, // 1 out of N allocations in the min/max range will be guarded (=1000) mi_option_guarded_sample_seed, // can be set to allow for a (more) deterministic re-execution when a guard page is triggered (=0) mi_option_target_segments_per_thread, // experimental (=0) + mi_option_generic_collect, // collect heaps every N (=10000) generic allocation calls _mi_option_last, // legacy option names mi_option_large_os_pages = mi_option_allow_large_os_pages, diff --git a/include/mimalloc/types.h b/include/mimalloc/types.h index c0e0f43b..1f4adc07 100644 --- a/include/mimalloc/types.h +++ b/include/mimalloc/types.h @@ -205,7 +205,9 @@ typedef int32_t mi_ssize_t; #define MI_LARGE_OBJ_WSIZE_MAX (MI_LARGE_OBJ_SIZE_MAX/MI_INTPTR_SIZE) // Maximum number of size classes. (spaced exponentially in 12.5% increments) -#define MI_BIN_HUGE (73U) +#if MI_BIN_HUGE != 73U +#error "mimalloc internal: expecting 73 bins" +#endif #if (MI_MEDIUM_OBJ_WSIZE_MAX >= 655360) #error "mimalloc internal: define more bins" @@ -560,6 +562,8 @@ struct mi_heap_s { size_t page_count; // total number of pages in the `pages` queues. size_t page_retired_min; // smallest retired index (retired pages are fully free, but still in the page queues) size_t page_retired_max; // largest retired index into the `pages` array. + long generic_count; // how often is `_mi_malloc_generic` called? + long generic_collect_count; // how often is `_mi_malloc_generic` called without collecting? mi_heap_t* next; // list of heaps per thread bool no_reclaim; // `true` if this heap should not reclaim abandoned pages uint8_t tag; // custom tag, can be used for separating heaps based on the object types diff --git a/src/arena.c b/src/arena.c index 6d6db9e2..36e9c0b0 100644 --- a/src/arena.c +++ b/src/arena.c @@ -554,7 +554,8 @@ static bool mi_arena_try_purge(mi_arena_t* arena, mi_msecs_t now, bool force) // reset expire (if not already set concurrently) mi_atomic_casi64_strong_acq_rel(&arena->purge_expire, &expire, (mi_msecs_t)0); - + _mi_stat_counter_increase(&_mi_stats_main.arena_purges, 1); + // potential purges scheduled, walk through the bitmap bool any_purged = false; bool full_purge = true; diff --git a/src/init.c b/src/init.c index 529cd31c..33a73701 100644 --- a/src/init.c +++ b/src/init.c @@ -77,9 +77,9 @@ const mi_page_t _mi_page_empty = { { 0 }, { 0 }, { 0 }, { 0 }, \ { 0 }, { 0 }, { 0 }, { 0 }, \ \ - { 0 }, { 0 }, { 0 }, { 0 }, \ - MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ - { 0 }, { 0 }, { 0 }, { 0 }, \ + { 0 }, { 0 }, { 0 }, { 0 }, { 0 }, \ + MI_INIT4(MI_STAT_COUNT_NULL), \ + { 0 }, { 0 }, { 0 }, { 0 }, \ \ { MI_INIT4(MI_STAT_COUNT_NULL) }, \ { { 0 }, { 0 }, { 0 }, { 0 } }, \ @@ -118,6 +118,7 @@ mi_decl_hidden mi_decl_cache_align const mi_heap_t _mi_heap_empty = { { {0}, {0}, 0, true }, // random 0, // page count MI_BIN_FULL, 0, // page retired min/max + 0, 0, // generic count NULL, // next false, // can reclaim 0, // tag @@ -166,6 +167,7 @@ mi_decl_cache_align mi_heap_t _mi_heap_main = { { {0x846ca68b}, {0}, 0, true }, // random 0, // page count MI_BIN_FULL, 0, // page retired min/max + 0, 0, // generic count NULL, // next heap false, // can reclaim 0, // tag diff --git a/src/options.c b/src/options.c index 3536e26b..8ce8cdba 100644 --- a/src/options.c +++ b/src/options.c @@ -162,6 +162,7 @@ static mi_option_desc_t options[_mi_option_last] = UNINIT, MI_OPTION(guarded_sample_rate)}, // 1 out of N allocations in the min/max range will be guarded (=4000) { 0, UNINIT, MI_OPTION(guarded_sample_seed)}, { 0, UNINIT, MI_OPTION(target_segments_per_thread) }, // abandon segments beyond this point, or 0 to disable. + { 10000, UNINIT, MI_OPTION(generic_collect) }, // collect heaps every N (=10000) generic allocation calls }; static void mi_option_init(mi_option_desc_t* desc); diff --git a/src/page.c b/src/page.c index ad3cc63d..5b9e7c40 100644 --- a/src/page.c +++ b/src/page.c @@ -989,11 +989,23 @@ void* _mi_malloc_generic(mi_heap_t* heap, size_t size, bool zero, size_t huge_al } mi_assert_internal(mi_heap_is_initialized(heap)); - // call potential deferred free routines - _mi_deferred_free(heap, false); + // do administrative tasks every N generic mallocs + if mi_unlikely(++heap->generic_count >= 100) { + heap->generic_collect_count += heap->generic_count; + heap->generic_count = 0; + // call potential deferred free routines + _mi_deferred_free(heap, false); - // free delayed frees from other threads (but skip contended ones) - _mi_heap_delayed_free_partial(heap); + // free delayed frees from other threads (but skip contended ones) + _mi_heap_delayed_free_partial(heap); + + // collect every once in a while (10000 by default) + const long generic_collect = mi_option_get_clamp(mi_option_generic_collect, 1, 1000000L); + if (heap->generic_collect_count >= generic_collect) { + heap->generic_collect_count = 0; + mi_heap_collect(heap, false /* force? */); + } + } // find (or allocate) a page of the right size mi_page_t* page = mi_find_page(heap, size, huge_alignment);