merge from dev3

This commit is contained in:
daanx 2024-12-21 15:52:15 -08:00
commit 5de5550c63
18 changed files with 835 additions and 653 deletions

View file

@ -272,14 +272,14 @@
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\test\main-override-static.c" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="mimalloc.vcxproj"> <ProjectReference Include="mimalloc.vcxproj">
<Project>{abb5eae7-b3e6-432e-b636-333449892ea6}</Project> <Project>{abb5eae7-b3e6-432e-b636-333449892ea6}</Project>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\test\main-override-static.c" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">
</ImportGroup> </ImportGroup>

View file

@ -279,7 +279,7 @@ mi_decl_export bool mi_manage_os_memory(void* start, size_t size, bool is_commit
mi_decl_export void mi_debug_show_arenas(bool show_pages, bool show_inuse, bool show_committed) mi_attr_noexcept; mi_decl_export void mi_debug_show_arenas(bool show_pages, bool show_inuse, bool show_committed) mi_attr_noexcept;
// Experimental: heaps associated with specific memory arena's // Experimental: heaps associated with specific memory arena's
typedef int mi_arena_id_t; typedef void* mi_arena_id_t;
mi_decl_export void* mi_arena_area(mi_arena_id_t arena_id, size_t* size); mi_decl_export void* mi_arena_area(mi_arena_id_t arena_id, size_t* size);
mi_decl_export 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; mi_decl_export 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;
mi_decl_export 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; mi_decl_export 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;
@ -326,7 +326,13 @@ mi_decl_export void mi_heap_guarded_set_size_bound(mi_heap_t* heap, size_t min,
//mi_decl_export void mi_os_decommit(void* p, size_t size); //mi_decl_export void mi_os_decommit(void* p, size_t size);
mi_decl_export bool mi_arena_unload(mi_arena_id_t arena_id, void** base, size_t* accessed_size, size_t* size); mi_decl_export bool mi_arena_unload(mi_arena_id_t arena_id, void** base, size_t* accessed_size, size_t* size);
mi_decl_export bool mi_arena_reload(void* start, size_t size, bool is_committed, bool is_large, bool is_zero, mi_arena_id_t* arena_id); mi_decl_export bool mi_arena_reload(void* start, size_t size, mi_arena_id_t* arena_id);
mi_decl_export bool mi_heap_reload(mi_heap_t* heap, mi_arena_id_t arena);
mi_decl_export void mi_heap_unload(mi_heap_t* heap);
// Is a pointer contained in the given arena area?
mi_decl_export bool mi_arena_contains(mi_arena_id_t arena_id, const void* p);
// ------------------------------------------------------ // ------------------------------------------------------
// Convenience // Convenience

View file

@ -1,5 +1,5 @@
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
Copyright (c) 2018-2023 Microsoft Research, Daan Leijen Copyright (c) 2018-2024 Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution. "LICENSE" at the root of this distribution.
@ -407,19 +407,45 @@ static inline void mi_atomic_yield(void) {
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Locks are only used for abandoned segment visiting in `arena.c` // Locks
// These should be light-weight in-process only locks.
// Only used for reserving arena's and to maintain the abandoned list.
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
#if _MSC_VER
#pragma warning(disable:26110) // unlock with holding lock
#endif
#define mi_lock(lock) for(bool _go = (mi_lock_acquire(lock),true); _go; (mi_lock_release(lock), _go=false) )
#if defined(_WIN32) #if defined(_WIN32)
#if 1
#define mi_lock_t SRWLOCK // slim reader-writer lock
static inline bool mi_lock_try_acquire(mi_lock_t* lock) {
return TryAcquireSRWLockExclusive(lock);
}
static inline void mi_lock_acquire(mi_lock_t* lock) {
AcquireSRWLockExclusive(lock);
}
static inline void mi_lock_release(mi_lock_t* lock) {
ReleaseSRWLockExclusive(lock);
}
static inline void mi_lock_init(mi_lock_t* lock) {
InitializeSRWLock(lock);
}
static inline void mi_lock_done(mi_lock_t* lock) {
(void)(lock);
}
#else
#define mi_lock_t CRITICAL_SECTION #define mi_lock_t CRITICAL_SECTION
static inline bool mi_lock_try_acquire(mi_lock_t* lock) { static inline bool mi_lock_try_acquire(mi_lock_t* lock) {
return TryEnterCriticalSection(lock); return TryEnterCriticalSection(lock);
} }
static inline bool mi_lock_acquire(mi_lock_t* lock) { static inline void mi_lock_acquire(mi_lock_t* lock) {
EnterCriticalSection(lock); EnterCriticalSection(lock);
return true;
} }
static inline void mi_lock_release(mi_lock_t* lock) { static inline void mi_lock_release(mi_lock_t* lock) {
LeaveCriticalSection(lock); LeaveCriticalSection(lock);
@ -431,16 +457,22 @@ static inline void mi_lock_done(mi_lock_t* lock) {
DeleteCriticalSection(lock); DeleteCriticalSection(lock);
} }
#endif
#elif defined(MI_USE_PTHREADS) #elif defined(MI_USE_PTHREADS)
void _mi_error_message(int err, const char* fmt, ...);
#define mi_lock_t pthread_mutex_t #define mi_lock_t pthread_mutex_t
static inline bool mi_lock_try_acquire(mi_lock_t* lock) { static inline bool mi_lock_try_acquire(mi_lock_t* lock) {
return (pthread_mutex_trylock(lock) == 0); return (pthread_mutex_trylock(lock) == 0);
} }
static inline bool mi_lock_acquire(mi_lock_t* lock) { static inline void mi_lock_acquire(mi_lock_t* lock) {
return (pthread_mutex_lock(lock) == 0); const int err = pthread_mutex_lock(lock);
if (err != 0) {
_mi_error_message(err, "internal error: lock cannot be acquired\n");
}
} }
static inline void mi_lock_release(mi_lock_t* lock) { static inline void mi_lock_release(mi_lock_t* lock) {
pthread_mutex_unlock(lock); pthread_mutex_unlock(lock);
@ -452,18 +484,16 @@ static inline void mi_lock_done(mi_lock_t* lock) {
pthread_mutex_destroy(lock); pthread_mutex_destroy(lock);
} }
/*
#elif defined(__cplusplus) #elif defined(__cplusplus)
#include <mutex> #include <mutex>
#define mi_lock_t std::mutex #define mi_lock_t std::mutex
static inline bool mi_lock_try_acquire(mi_lock_t* lock) { static inline bool mi_lock_try_acquire(mi_lock_t* lock) {
return lock->lock_try_acquire(); return lock->try_lock();
} }
static inline bool mi_lock_acquire(mi_lock_t* lock) { static inline void mi_lock_acquire(mi_lock_t* lock) {
lock->lock(); lock->lock();
return true;
} }
static inline void mi_lock_release(mi_lock_t* lock) { static inline void mi_lock_release(mi_lock_t* lock) {
lock->unlock(); lock->unlock();
@ -474,7 +504,6 @@ static inline void mi_lock_init(mi_lock_t* lock) {
static inline void mi_lock_done(mi_lock_t* lock) { static inline void mi_lock_done(mi_lock_t* lock) {
(void)(lock); (void)(lock);
} }
*/
#else #else
@ -487,12 +516,11 @@ static inline bool mi_lock_try_acquire(mi_lock_t* lock) {
uintptr_t expected = 0; uintptr_t expected = 0;
return mi_atomic_cas_strong_acq_rel(lock, &expected, (uintptr_t)1); return mi_atomic_cas_strong_acq_rel(lock, &expected, (uintptr_t)1);
} }
static inline bool mi_lock_acquire(mi_lock_t* lock) { static inline void mi_lock_acquire(mi_lock_t* lock) {
for (int i = 0; i < 1000; i++) { // for at most 1000 tries? for (int i = 0; i < 1000; i++) { // for at most 1000 tries?
if (mi_lock_try_acquire(lock)) return true; if (mi_lock_try_acquire(lock)) return;
mi_atomic_yield(); mi_atomic_yield();
} }
return true;
} }
static inline void mi_lock_release(mi_lock_t* lock) { static inline void mi_lock_release(mi_lock_t* lock) {
mi_atomic_store_release(lock, (uintptr_t)0); mi_atomic_store_release(lock, (uintptr_t)0);
@ -507,6 +535,4 @@ static inline void mi_lock_done(mi_lock_t* lock) {
#endif #endif
#endif // MI_ATOMIC_H #endif // MI_ATOMIC_H

View file

@ -90,7 +90,6 @@ uintptr_t _mi_os_random_weak(uintptr_t extra_seed);
static inline uintptr_t _mi_random_shuffle(uintptr_t x); static inline uintptr_t _mi_random_shuffle(uintptr_t x);
// init.c // init.c
extern mi_decl_cache_align mi_stats_t _mi_stats_main;
extern mi_decl_cache_align const mi_page_t _mi_page_empty; extern mi_decl_cache_align const mi_page_t _mi_page_empty;
void _mi_process_load(void); void _mi_process_load(void);
void mi_cdecl _mi_process_done(void); void mi_cdecl _mi_process_done(void);
@ -101,8 +100,10 @@ bool _mi_is_main_thread(void);
size_t _mi_current_thread_count(void); size_t _mi_current_thread_count(void);
bool _mi_preloading(void); // true while the C runtime is not initialized yet bool _mi_preloading(void); // true while the C runtime is not initialized yet
void _mi_thread_done(mi_heap_t* heap); void _mi_thread_done(mi_heap_t* heap);
mi_tld_t* _mi_tld(void); // current tld: `_mi_tld() == _mi_heap_get_default()->tld`
mi_tld_t* _mi_tld(void); // current tld: `_mi_tld() == _mi_heap_get_default()->tld`
mi_subproc_t* _mi_subproc(void);
mi_subproc_t* _mi_subproc_main(void);
mi_threadid_t _mi_thread_id(void) mi_attr_noexcept; mi_threadid_t _mi_thread_id(void) mi_attr_noexcept;
size_t _mi_thread_seq_id(void) mi_attr_noexcept; size_t _mi_thread_seq_id(void) mi_attr_noexcept;
@ -142,10 +143,12 @@ void* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t m
// arena.c // arena.c
mi_arena_id_t _mi_arena_id_none(void); mi_arena_id_t _mi_arena_id_none(void);
void _mi_arena_init(void); mi_arena_t* _mi_arena_from_id(mi_arena_id_t id);
void* _mi_arena_alloc(size_t size, bool commit, bool allow_large, mi_arena_id_t req_arena_id, size_t tseq, mi_memid_t* memid);
void* _mi_arena_alloc_aligned(size_t size, size_t alignment, size_t align_offset, bool commit, bool allow_large, mi_arena_id_t req_arena_id, size_t tseq, mi_memid_t* memid); void* _mi_arena_alloc(mi_subproc_t* subproc, size_t size, bool commit, bool allow_large, mi_arena_t* req_arena, size_t tseq, mi_memid_t* memid);
bool _mi_arena_memid_is_suitable(mi_memid_t memid, mi_arena_id_t request_arena_id); void* _mi_arena_alloc_aligned(mi_subproc_t* subproc, size_t size, size_t alignment, size_t align_offset, bool commit, bool allow_large, mi_arena_t* req_arena, size_t tseq, mi_memid_t* memid);
void _mi_arena_free(void* p, size_t size, mi_memid_t memid);
bool _mi_arena_memid_is_suitable(mi_memid_t memid, mi_arena_t* request_arena);
bool _mi_arena_contains(const void* p); bool _mi_arena_contains(const void* p);
void _mi_arenas_collect(bool force_purge); void _mi_arenas_collect(bool force_purge);
void _mi_arena_unsafe_destroy_all(void); void _mi_arena_unsafe_destroy_all(void);
@ -201,6 +204,7 @@ void _mi_heap_page_reclaim(mi_heap_t* heap, mi_page_t* page);
// "stats.c" // "stats.c"
void _mi_stats_done(mi_stats_t* stats); void _mi_stats_done(mi_stats_t* stats);
void _mi_stats_merge_from(mi_stats_t* to, mi_stats_t* from);
mi_msecs_t _mi_clock_now(void); mi_msecs_t _mi_clock_now(void);
mi_msecs_t _mi_clock_end(mi_msecs_t start); mi_msecs_t _mi_clock_end(mi_msecs_t start);
mi_msecs_t _mi_clock_start(void); mi_msecs_t _mi_clock_start(void);
@ -418,11 +422,11 @@ static inline bool mi_heap_is_initialized(mi_heap_t* heap) {
return (heap != &_mi_heap_empty); return (heap != &_mi_heap_empty);
} }
static inline uintptr_t _mi_ptr_cookie(const void* p) { //static inline uintptr_t _mi_ptr_cookie(const void* p) {
extern mi_heap_t _mi_heap_main; // extern mi_heap_t _mi_heap_main;
mi_assert_internal(_mi_heap_main.cookie != 0); // mi_assert_internal(_mi_heap_main.cookie != 0);
return ((uintptr_t)p ^ _mi_heap_main.cookie); // return ((uintptr_t)p ^ _mi_heap_main.cookie);
} //}
/* ----------------------------------------------------------- /* -----------------------------------------------------------
@ -524,7 +528,7 @@ static inline void mi_page_set_heap(mi_page_t* page, mi_heap_t* heap) {
if (heap != NULL) { if (heap != NULL) {
page->heap = heap; page->heap = heap;
page->heap_tag = heap->tag; page->heap_tag = heap->tag;
mi_atomic_store_release(&page->xthread_id, heap->thread_id); mi_atomic_store_release(&page->xthread_id, heap->tld->thread_id);
} }
else { else {
page->heap = NULL; page->heap = NULL;

View file

@ -243,9 +243,6 @@ typedef size_t mi_page_flags_t;
// atomically in `free.c:mi_free_block_mt`. // atomically in `free.c:mi_free_block_mt`.
typedef uintptr_t mi_thread_free_t; typedef uintptr_t mi_thread_free_t;
// Sub processes are used to keep memory separate between them (e.g. multiple interpreters in CPython)
typedef struct mi_subproc_s mi_subproc_t;
// A heap can serve only specific objects signified by its heap tag (e.g. various object types in CPython) // A heap can serve only specific objects signified by its heap tag (e.g. various object types in CPython)
typedef uint8_t mi_heaptag_t; typedef uint8_t mi_heaptag_t;
@ -296,10 +293,9 @@ typedef struct mi_page_s {
uintptr_t keys[2]; // two random keys to encode the free lists (see `_mi_block_next`) or padding canary uintptr_t keys[2]; // two random keys to encode the free lists (see `_mi_block_next`) or padding canary
#endif #endif
mi_heap_t* heap; // heap this threads belong to. mi_heap_t* heap; // the heap owning this page (or NULL for abandoned pages)
struct mi_page_s* next; // next page owned by the heap with the same `block_size` struct mi_page_s* next; // next page owned by the heap with the same `block_size`
struct mi_page_s* prev; // previous page owned by the heap with the same `block_size` struct mi_page_s* prev; // previous page owned by the heap with the same `block_size`
mi_subproc_t* subproc; // sub-process of this heap
mi_memid_t memid; // provenance of the page memory mi_memid_t memid; // provenance of the page memory
} mi_page_t; } mi_page_t;
@ -380,7 +376,7 @@ typedef struct mi_random_cxt_s {
// In debug mode there is a padding structure at the end of the blocks to check for buffer overflows // In debug mode there is a padding structure at the end of the blocks to check for buffer overflows
#if (MI_PADDING) #if MI_PADDING
typedef struct mi_padding_s { typedef struct mi_padding_s {
uint32_t canary; // encoded block value to check validity of the padding (in case of overflow) uint32_t canary; // encoded block value to check validity of the padding (in case of overflow)
uint32_t delta; // padding bytes before the block. (mi_usable_size(p) - delta == exact allocated bytes) uint32_t delta; // padding bytes before the block. (mi_usable_size(p) - delta == exact allocated bytes)
@ -397,19 +393,14 @@ typedef struct mi_padding_s {
// A heap owns a set of pages. // A heap owns a set of pages.
struct mi_heap_s { struct mi_heap_s {
mi_tld_t* tld; mi_tld_t* tld; // thread-local data
// _Atomic(mi_block_t*) thread_delayed_free; mi_arena_t* exclusive_arena; // if the heap should only allocate from a specific arena (or NULL)
mi_threadid_t thread_id; // thread this heap belongs too
mi_arena_id_t arena_id; // arena id if the heap belongs to a specific arena (or 0)
uintptr_t cookie; // random cookie to verify pointers (see `_mi_ptr_cookie`) uintptr_t cookie; // random cookie to verify pointers (see `_mi_ptr_cookie`)
uintptr_t keys[2]; // two random keys used to encode the `thread_delayed_free` list
mi_random_ctx_t random; // random number context used for secure allocation mi_random_ctx_t random; // random number context used for secure allocation
size_t page_count; // total number of pages in the `pages` queues. 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_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. size_t page_retired_max; // largest retired index into the `pages` array.
mi_heap_t* next; // list of heaps per thread mi_heap_t* next; // list of heaps per thread
mi_memid_t memid; // provenance of the heap struct itseft (meta or os)
long generic_count;
long full_page_retain; // how many full pages can be retained per queue (before abondoning them) long full_page_retain; // how many full pages can be retained per queue (before abondoning them)
bool allow_page_reclaim; // `true` if this heap should not reclaim abandoned pages bool allow_page_reclaim; // `true` if this heap should not reclaim abandoned pages
bool allow_page_abandon; // `true` if this heap can abandon pages to reduce memory footprint bool allow_page_abandon; // `true` if this heap can abandon pages to reduce memory footprint
@ -422,7 +413,8 @@ struct mi_heap_s {
size_t guarded_sample_count; // current sample count (counting down to 0) size_t guarded_sample_count; // current sample count (counting down to 0)
#endif #endif
mi_page_t* pages_free_direct[MI_PAGES_DIRECT]; // optimize: array where every entry points a page with possibly free blocks in the corresponding queue for that size. mi_page_t* pages_free_direct[MI_PAGES_DIRECT]; // optimize: array where every entry points a page with possibly free blocks in the corresponding queue for that size.
mi_page_queue_t pages[MI_BIN_FULL + 1]; // queue of pages for each size class (or "bin") mi_page_queue_t pages[MI_BIN_COUNT]; // queue of pages for each size class (or "bin")
mi_memid_t memid; // provenance of the heap struct itself (meta or os)
}; };
@ -451,18 +443,18 @@ typedef struct mi_stat_counter_s {
} mi_stat_counter_t; } mi_stat_counter_t;
typedef struct mi_stats_s { typedef struct mi_stats_s {
mi_stat_count_t pages; mi_stat_count_t pages;
mi_stat_count_t reserved; mi_stat_count_t reserved;
mi_stat_count_t committed; mi_stat_count_t committed;
mi_stat_count_t reset; mi_stat_count_t reset;
mi_stat_count_t purged; mi_stat_count_t purged;
mi_stat_count_t page_committed; mi_stat_count_t page_committed;
mi_stat_count_t pages_abandoned; mi_stat_count_t pages_abandoned;
mi_stat_count_t threads; mi_stat_count_t threads;
mi_stat_count_t normal; mi_stat_count_t normal;
mi_stat_count_t huge; mi_stat_count_t huge;
mi_stat_count_t giant; mi_stat_count_t giant;
mi_stat_count_t malloc; mi_stat_count_t malloc;
mi_stat_counter_t pages_extended; mi_stat_counter_t pages_extended;
mi_stat_counter_t pages_reclaim_on_alloc; mi_stat_counter_t pages_reclaim_on_alloc;
mi_stat_counter_t pages_reclaim_on_free; mi_stat_counter_t pages_reclaim_on_free;
@ -480,53 +472,89 @@ typedef struct mi_stats_s {
mi_stat_counter_t arena_count; mi_stat_counter_t arena_count;
mi_stat_counter_t guarded_alloc_count; mi_stat_counter_t guarded_alloc_count;
#if MI_STAT>1 #if MI_STAT>1
mi_stat_count_t normal_bins[MI_BIN_HUGE+1]; mi_stat_count_t normal_bins[MI_BIN_COUNT];
#endif #endif
} mi_stats_t; } mi_stats_t;
// add to stat keeping track of the peak // add to stat keeping track of the peak
void _mi_stat_increase(mi_stat_count_t* stat, size_t amount); void __mi_stat_increase(mi_stat_count_t* stat, size_t amount);
void _mi_stat_decrease(mi_stat_count_t* stat, size_t amount); void __mi_stat_decrease(mi_stat_count_t* stat, size_t amount);
void __mi_stat_increase_mt(mi_stat_count_t* stat, size_t amount);
void __mi_stat_decrease_mt(mi_stat_count_t* stat, size_t amount);
// adjust stat in special cases to compensate for double counting // adjust stat in special cases to compensate for double counting
void _mi_stat_adjust_increase(mi_stat_count_t* stat, size_t amount, bool on_alloc); void __mi_stat_adjust_increase(mi_stat_count_t* stat, size_t amount, bool on_alloc);
void _mi_stat_adjust_decrease(mi_stat_count_t* stat, size_t amount, bool on_free); void __mi_stat_adjust_decrease(mi_stat_count_t* stat, size_t amount, bool on_free);
void __mi_stat_adjust_increase_mt(mi_stat_count_t* stat, size_t amount, bool on_alloc);
void __mi_stat_adjust_decrease_mt(mi_stat_count_t* stat, size_t amount, bool on_free);
// counters can just be increased // counters can just be increased
void _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount); void __mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount);
void __mi_stat_counter_increase_mt(mi_stat_counter_t* stat, size_t amount);
#if (MI_STAT) #if (MI_STAT)
#define mi_stat_increase(stat,amount) _mi_stat_increase( &(stat), amount) #define mi_debug_stat_increase(stat,amount) __mi_stat_increase( &(stat), amount)
#define mi_stat_decrease(stat,amount) _mi_stat_decrease( &(stat), amount) #define mi_debug_stat_decrease(stat,amount) __mi_stat_decrease( &(stat), amount)
#define mi_stat_counter_increase(stat,amount) _mi_stat_counter_increase( &(stat), amount) #define mi_debug_stat_counter_increase(stat,amount) __mi_stat_counter_increase( &(stat), amount)
#define mi_stat_adjust_increase(stat,amnt,b) _mi_stat_adjust_increase( &(stat), amnt, b) #define mi_debug_stat_increase_mt(stat,amount) __mi_stat_increase_mt( &(stat), amount)
#define mi_stat_adjust_decrease(stat,amnt,b) _mi_stat_adjust_decrease( &(stat), amnt, b) #define mi_debug_stat_decrease_mt(stat,amount) __mi_stat_decrease_mt( &(stat), amount)
#define mi_debug_stat_counter_increase_mt(stat,amount) __mi_stat_counter_increase_mt( &(stat), amount)
#define mi_debug_stat_adjust_increase_mt(stat,amnt,b) __mi_stat_adjust_increase_mt( &(stat), amnt, b)
#define mi_debug_stat_adjust_decrease_mt(stat,amnt,b) __mi_stat_adjust_decrease_mt( &(stat), amnt, b)
#else #else
#define mi_stat_increase(stat,amount) ((void)0) #define mi_debug_stat_increase(stat,amount) ((void)0)
#define mi_stat_decrease(stat,amount) ((void)0) #define mi_debug_stat_decrease(stat,amount) ((void)0)
#define mi_stat_counter_increase(stat,amount) ((void)0) #define mi_debug_stat_counter_increase(stat,amount) ((void)0)
#define mi_stat_adjuct_increase(stat,amnt,b) ((void)0) #define mi_debug_stat_increase_mt(stat,amount) ((void)0)
#define mi_stat_adjust_decrease(stat,amnt,b) ((void)0) #define mi_debug_stat_decrease_mt(stat,amount) ((void)0)
#define mi_debug_stat_counter_increase_mt(stat,amount) ((void)0)
#define mi_debug_stat_adjust_increase(stat,amnt,b) ((void)0)
#define mi_debug_stat_adjust_decrease(stat,amnt,b) ((void)0)
#endif #endif
#define mi_heap_stat_counter_increase(heap,stat,amount) mi_stat_counter_increase( (heap)->tld->stats.stat, amount) #define mi_subproc_stat_counter_increase(subproc,stat,amount) __mi_stat_counter_increase_mt( &(subproc)->stats.stat, amount)
#define mi_heap_stat_increase(heap,stat,amount) mi_stat_increase( (heap)->tld->stats.stat, amount) #define mi_subproc_stat_increase(subproc,stat,amount) __mi_stat_increase_mt( &(subproc)->stats.stat, amount)
#define mi_heap_stat_decrease(heap,stat,amount) mi_stat_decrease( (heap)->tld->stats.stat, amount) #define mi_subproc_stat_decrease(subproc,stat,amount) __mi_stat_decrease_mt( &(subproc)->stats.stat, amount)
#define mi_subproc_stat_adjust_increase(subproc,stat,amnt,b) __mi_stat_adjust_increase_mt( &(subproc)->stats.stat, amnt, b)
#define mi_subproc_stat_adjust_decrease(subproc,stat,amnt,b) __mi_stat_adjust_decrease_mt( &(subproc)->stats.stat, amnt, b)
#define mi_os_stat_counter_increase(stat,amount) mi_subproc_stat_counter_increase(_mi_subproc(),stat,amount)
#define mi_os_stat_increase(stat,amount) mi_subproc_stat_increase(_mi_subproc(),stat,amount)
#define mi_os_stat_decrease(stat,amount) mi_subproc_stat_decrease(_mi_subproc(),stat,amount)
#define mi_heap_stat_counter_increase(heap,stat,amount) __mi_stat_counter_increase( &(heap)->tld->stats.stat, amount)
#define mi_heap_stat_increase(heap,stat,amount) __mi_stat_increase( &(heap)->tld->stats.stat, amount)
#define mi_heap_stat_decrease(heap,stat,amount) __mi_stat_decrease( &(heap)->tld->stats.stat, amount)
#define mi_debug_heap_stat_counter_increase(heap,stat,amount) mi_debug_stat_counter_increase( (heap)->tld->stats.stat, amount)
#define mi_debug_heap_stat_increase(heap,stat,amount) mi_debug_stat_increase( (heap)->tld->stats.stat, amount)
#define mi_debug_heap_stat_decrease(heap,stat,amount) mi_debug_stat_decrease( (heap)->tld->stats.stat, amount)
// ------------------------------------------------------ // ------------------------------------------------------
// Sub processes do not reclaim or visit segments // Sub processes use separate arena's and no heaps/pages/blocks
// from other sub processes // are shared between sub processes.
// The subprocess structure contains essentially all static variables (except per subprocess :-))
//
// Each thread should belong to one sub-process only
// ------------------------------------------------------ // ------------------------------------------------------
struct mi_subproc_s { #define MI_MAX_ARENAS (160) // Limited for now (and takes up .bss).. but arena's scale up exponentially (see `mi_arena_reserve`)
_Atomic(size_t) abandoned_count[MI_BIN_COUNT]; // count of abandoned pages for this sub-process // 160 arenas is enough for ~2 TiB memory
_Atomic(size_t) abandoned_os_list_count; // count of abandoned pages in the os-list
mi_lock_t abandoned_os_lock; // lock for the abandoned os pages list (outside of arena's) (this lock protect list operations) typedef struct mi_subproc_s {
mi_lock_t abandoned_os_visit_lock; // ensure only one thread per subproc visits the abandoned os list _Atomic(size_t) arena_count; // current count of arena's
mi_page_t* abandoned_os_list; // doubly-linked list of abandoned pages outside of arena's (in OS allocated memory) _Atomic(mi_arena_t*) arenas[MI_MAX_ARENAS]; // arena's of this sub-process
mi_page_t* abandoned_os_list_tail; // the tail-end of the list mi_lock_t arena_reserve_lock; // lock to ensure arena's get reserved one at a time
mi_memid_t memid; // provenance of this memory block _Atomic(int64_t) purge_expire; // expiration is set if any arenas can be purged
};
_Atomic(size_t) abandoned_count[MI_BIN_COUNT]; // total count of abandoned pages for this sub-process
mi_page_t* os_abandoned_pages; // list of pages that OS allocated and not in an arena (only used if `mi_option_visit_abandoned` is on)
mi_lock_t os_abandoned_pages_lock; // lock for the os abandoned pages list (this lock protects list operations)
mi_memid_t memid; // provenance of this memory block (meta or OS)
mi_stats_t stats; // sub-process statistics (tld stats are merged in on thread termination)
} mi_subproc_t;
// ------------------------------------------------------ // ------------------------------------------------------
// Thread Local data // Thread Local data
@ -535,20 +563,21 @@ struct mi_subproc_s {
// Milliseconds as in `int64_t` to avoid overflows // Milliseconds as in `int64_t` to avoid overflows
typedef int64_t mi_msecs_t; typedef int64_t mi_msecs_t;
// Thread local data // Thread local data
struct mi_tld_s { struct mi_tld_s {
unsigned long long heartbeat; // monotonic heartbeat count mi_threadid_t thread_id; // thread id of this thread
mi_heap_t* heap_backing; // backing heap of this thread (cannot be deleted) size_t thread_seq; // thread sequence id (linear count of created threads)
mi_heap_t* heaps; // list of heaps in this thread (so we can abandon all when the thread terminates) mi_subproc_t* subproc; // sub-process this thread belongs to.
mi_subproc_t* subproc; // sub-process this thread belongs to. mi_heap_t* heap_backing; // backing heap of this thread (cannot be deleted)
size_t tseq; // thread sequence id mi_heap_t* heaps; // list of heaps in this thread (so we can abandon all when the thread terminates)
mi_memid_t memid; // provenance of the tld memory itself (meta or OS) unsigned long long heartbeat; // monotonic heartbeat count
bool recurse; // true if deferred was called; used to prevent infinite recursion. bool recurse; // true if deferred was called; used to prevent infinite recursion.
bool is_in_threadpool; // true if this thread is part of a threadpool (and can run arbitrary tasks) bool is_in_threadpool; // true if this thread is part of a threadpool (and can run arbitrary tasks)
mi_stats_t stats; // statistics mi_stats_t stats; // statistics
mi_memid_t memid; // provenance of the tld memory itself (meta or OS)
}; };
/* ----------------------------------------------------------- /* -----------------------------------------------------------
Error codes passed to `_mi_fatal_error` Error codes passed to `_mi_fatal_error`
All are recoverable but EFAULT is a serious error and aborts by default in secure mode. All are recoverable but EFAULT is a serious error and aborts by default in secure mode.

View file

@ -193,9 +193,7 @@ static void* mi_heap_malloc_zero_aligned_at(mi_heap_t* const heap, const size_t
const bool is_aligned = (((uintptr_t)page->free + offset) & align_mask)==0; const bool is_aligned = (((uintptr_t)page->free + offset) & align_mask)==0;
if mi_likely(is_aligned) if mi_likely(is_aligned)
{ {
#if MI_STAT>1 mi_debug_heap_stat_increase(heap, malloc, size);
mi_heap_stat_increase(heap, malloc, size);
#endif
void* p = (zero ? _mi_page_malloc_zeroed(heap,page,padsize) : _mi_page_malloc(heap,page,padsize)); // call specific page malloc for better codegen void* p = (zero ? _mi_page_malloc_zeroed(heap,page,padsize) : _mi_page_malloc(heap,page,padsize)); // call specific page malloc for better codegen
mi_assert_internal(p != NULL); mi_assert_internal(p != NULL);
mi_assert_internal(((uintptr_t)p + offset) % alignment == 0); mi_assert_internal(((uintptr_t)p + offset) % alignment == 0);

View file

@ -134,7 +134,7 @@ static inline mi_decl_restrict void* mi_heap_malloc_small_zero(mi_heap_t* heap,
mi_assert(size <= MI_SMALL_SIZE_MAX); mi_assert(size <= MI_SMALL_SIZE_MAX);
#if MI_DEBUG #if MI_DEBUG
const uintptr_t tid = _mi_thread_id(); const uintptr_t tid = _mi_thread_id();
mi_assert(heap->thread_id == 0 || heap->thread_id == tid); // heaps are thread local mi_assert(heap->tld->thread_id == 0 || heap->tld->thread_id == tid); // heaps are thread local
#endif #endif
#if (MI_PADDING || MI_GUARDED) #if (MI_PADDING || MI_GUARDED)
if (size == 0) { size = sizeof(void*); } if (size == 0) { size = sizeof(void*); }
@ -188,7 +188,7 @@ extern inline void* _mi_heap_malloc_zero_ex(mi_heap_t* heap, size_t size, bool z
else { else {
// regular allocation // regular allocation
mi_assert(heap!=NULL); mi_assert(heap!=NULL);
mi_assert(heap->thread_id == 0 || heap->thread_id == _mi_thread_id()); // heaps are thread local mi_assert(heap->tld->thread_id == 0 || heap->tld->thread_id == _mi_thread_id()); // heaps are thread local
void* const p = _mi_malloc_generic(heap, size + MI_PADDING_SIZE, zero, huge_alignment); // note: size can overflow but it is detected in malloc_generic void* const p = _mi_malloc_generic(heap, size + MI_PADDING_SIZE, zero, huge_alignment); // note: size can overflow but it is detected in malloc_generic
mi_track_malloc(p,size,zero); mi_track_malloc(p,size,zero);

View file

@ -64,10 +64,11 @@ static void* mi_meta_block_start( mi_meta_page_t* mpage, size_t block_idx ) {
// allocate a fresh meta page and add it to the global list. // allocate a fresh meta page and add it to the global list.
static mi_meta_page_t* mi_meta_page_zalloc(void) { static mi_meta_page_t* mi_meta_page_zalloc(void) {
// allocate a fresh arena slice // allocate a fresh arena slice
// note: careful with _mi_subproc as it may recurse into mi_tld and meta_page_zalloc again..
mi_memid_t memid; mi_memid_t memid;
mi_meta_page_t* mpage = (mi_meta_page_t*)_mi_arena_alloc_aligned(MI_ARENA_SLICE_SIZE, MI_ARENA_SLICE_ALIGN, 0, mi_meta_page_t* mpage = (mi_meta_page_t*)_mi_arena_alloc_aligned(_mi_subproc(), MI_ARENA_SLICE_SIZE, MI_ARENA_SLICE_ALIGN, 0,
true /* commit*/, true /* allow large */, true /* commit*/, true /* allow large */,
_mi_arena_id_none(), 0 /* tseq */, &memid ); NULL /* req arena */, 0 /* thread_seq */, &memid);
if (mpage == NULL) return NULL; if (mpage == NULL) return NULL;
mi_assert_internal(_mi_is_aligned(mpage,MI_META_PAGE_ALIGN)); mi_assert_internal(_mi_is_aligned(mpage,MI_META_PAGE_ALIGN));
if (!memid.initially_zero) { if (!memid.initially_zero) {
@ -147,11 +148,8 @@ mi_decl_noinline void _mi_meta_free(void* p, size_t size, mi_memid_t memid) {
_mi_memzero_aligned(mi_meta_block_start(mpage, block_idx), block_count*MI_META_BLOCK_SIZE); _mi_memzero_aligned(mi_meta_block_start(mpage, block_idx), block_count*MI_META_BLOCK_SIZE);
mi_bbitmap_setN(&mpage->blocks_free, block_idx, block_count); mi_bbitmap_setN(&mpage->blocks_free, block_idx, block_count);
} }
else if (mi_memid_is_os(memid)) {
_mi_os_free(p, size, memid);
}
else { else {
mi_assert_internal(mi_memid_needs_no_free(memid)); _mi_arena_free(p,size,memid);
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -114,7 +114,9 @@ static inline void mi_bfield_atomic_clear_once_set(_Atomic(mi_bfield_t)*b, size_
do { do {
if mi_unlikely((old&mask) == 0) { if mi_unlikely((old&mask) == 0) {
old = mi_atomic_load_acquire(b); old = mi_atomic_load_acquire(b);
if ((old&mask)==0) { _mi_stat_counter_increase(&_mi_stats_main.pages_unabandon_busy_wait, 1); } if ((old&mask)==0) {
mi_subproc_stat_counter_increase(_mi_subproc(), pages_unabandon_busy_wait, 1);
}
while ((old&mask)==0) { // busy wait while ((old&mask)==0) { // busy wait
mi_atomic_yield(); mi_atomic_yield();
old = mi_atomic_load_acquire(b); old = mi_atomic_load_acquire(b);
@ -1151,7 +1153,6 @@ static inline bool mi_bitmap_find(mi_bitmap_t* bitmap, size_t tseq, size_t n, si
typedef struct mi_claim_fun_data_s { typedef struct mi_claim_fun_data_s {
mi_arena_t* arena; mi_arena_t* arena;
mi_subproc_t* subproc;
mi_heaptag_t heap_tag; mi_heaptag_t heap_tag;
} mi_claim_fun_data_t; } mi_claim_fun_data_t;
@ -1165,7 +1166,7 @@ static bool mi_bitmap_try_find_and_claim_visit(mi_bitmap_t* bitmap, size_t chunk
const size_t slice_index = (chunk_idx * MI_BCHUNK_BITS) + cidx; const size_t slice_index = (chunk_idx * MI_BCHUNK_BITS) + cidx;
mi_assert_internal(slice_index < mi_bitmap_max_bits(bitmap)); mi_assert_internal(slice_index < mi_bitmap_max_bits(bitmap));
bool keep_set = true; bool keep_set = true;
if ((*claim_fun)(slice_index, claim_data->arena, claim_data->subproc, claim_data->heap_tag, &keep_set)) { if ((*claim_fun)(slice_index, claim_data->arena, claim_data->heap_tag, &keep_set)) {
// success! // success!
mi_assert_internal(!keep_set); mi_assert_internal(!keep_set);
*pidx = slice_index; *pidx = slice_index;
@ -1190,9 +1191,9 @@ static bool mi_bitmap_try_find_and_claim_visit(mi_bitmap_t* bitmap, size_t chunk
// Find a set bit in the bitmap and try to atomically clear it and claim it. // Find a set bit in the bitmap and try to atomically clear it and claim it.
// (Used to find pages in the pages_abandoned bitmaps.) // (Used to find pages in the pages_abandoned bitmaps.)
mi_decl_nodiscard bool mi_bitmap_try_find_and_claim(mi_bitmap_t* bitmap, size_t tseq, size_t* pidx, mi_decl_nodiscard bool mi_bitmap_try_find_and_claim(mi_bitmap_t* bitmap, size_t tseq, size_t* pidx,
mi_claim_fun_t* claim, mi_arena_t* arena, mi_subproc_t* subproc, mi_heaptag_t heap_tag) mi_claim_fun_t* claim, mi_arena_t* arena, mi_heaptag_t heap_tag)
{ {
mi_claim_fun_data_t claim_data = { arena, subproc, heap_tag }; mi_claim_fun_data_t claim_data = { arena, heap_tag };
return mi_bitmap_find(bitmap, tseq, 1, pidx, &mi_bitmap_try_find_and_claim_visit, (void*)claim, &claim_data); return mi_bitmap_find(bitmap, tseq, 1, pidx, &mi_bitmap_try_find_and_claim_visit, (void*)claim, &claim_data);
} }

View file

@ -177,13 +177,13 @@ static inline bool mi_bitmap_is_clear(mi_bitmap_t* bitmap, size_t idx) {
// Called once a bit is cleared to see if the memory slice can be claimed. // Called once a bit is cleared to see if the memory slice can be claimed.
typedef bool (mi_claim_fun_t)(size_t slice_index, mi_arena_t* arena, mi_subproc_t* subproc, mi_heaptag_t heap_tag, bool* keep_set); typedef bool (mi_claim_fun_t)(size_t slice_index, mi_arena_t* arena, mi_heaptag_t heap_tag, bool* keep_set);
// Find a set bits in the bitmap, atomically clear it, and check if `claim` returns true. // Find a set bits in the bitmap, atomically clear it, and check if `claim` returns true.
// If not claimed, continue on (potentially setting the bit again depending on `keep_set`). // If not claimed, continue on (potentially setting the bit again depending on `keep_set`).
// Returns true on success, and in that case sets the index: `0 <= *pidx <= MI_BITMAP_MAX_BITS-n`. // Returns true on success, and in that case sets the index: `0 <= *pidx <= MI_BITMAP_MAX_BITS-n`.
mi_decl_nodiscard bool mi_bitmap_try_find_and_claim(mi_bitmap_t* bitmap, size_t tseq, size_t* pidx, mi_decl_nodiscard bool mi_bitmap_try_find_and_claim(mi_bitmap_t* bitmap, size_t tseq, size_t* pidx,
mi_claim_fun_t* claim, mi_arena_t* arena, mi_subproc_t* subproc, mi_heaptag_t heap_tag ); mi_claim_fun_t* claim, mi_arena_t* arena, mi_heaptag_t heap_tag );
// Atomically clear a bit but only if it is set. Will block otherwise until the bit is set. // Atomically clear a bit but only if it is set. Will block otherwise until the bit is set.

View file

@ -210,7 +210,7 @@ static void mi_decl_noinline mi_free_try_collect_mt(mi_page_t* page) {
if (mi_page_all_free(page)) if (mi_page_all_free(page))
{ {
// first remove it from the abandoned pages in the arena (if mapped, this waits for any readers to finish) // first remove it from the abandoned pages in the arena (if mapped, this waits for any readers to finish)
_mi_arena_page_unabandon(page); _mi_arena_page_unabandon(page);
// we can free the page directly // we can free the page directly
_mi_arena_page_free(page); _mi_arena_page_free(page);
return; return;
@ -234,15 +234,15 @@ static void mi_decl_noinline mi_free_try_collect_mt(mi_page_t* page) {
mi_heap_t* const tagheap = _mi_heap_by_tag(heap, page->heap_tag); mi_heap_t* const tagheap = _mi_heap_by_tag(heap, page->heap_tag);
if ((tagheap != NULL) && // don't reclaim across heap object types if ((tagheap != NULL) && // don't reclaim across heap object types
(tagheap->allow_page_reclaim) && // we are allowed to reclaim abandoned pages (tagheap->allow_page_reclaim) && // we are allowed to reclaim abandoned pages
(page->subproc == tagheap->tld->subproc) && // don't reclaim across sub-processes; todo: make this check faster (integrate with _mi_heap_by_tag ? ) // (page->subproc == tagheap->tld->subproc) && // don't reclaim across sub-processes; todo: make this check faster (integrate with _mi_heap_by_tag ? )
(_mi_arena_memid_is_suitable(page->memid, tagheap->arena_id)) // don't reclaim across unsuitable arena's; todo: inline arena_is_suitable (?) (_mi_arena_memid_is_suitable(page->memid, tagheap->exclusive_arena)) // don't reclaim across unsuitable arena's; todo: inline arena_is_suitable (?)
) )
{ {
if (mi_page_queue(tagheap, page->block_size)->first != NULL) { // don't reclaim for an block_size we don't use if (mi_page_queue(tagheap, page->block_size)->first != NULL) { // don't reclaim for an block_size we don't use
// first remove it from the abandoned pages in the arena -- this waits for any readers to finish // first remove it from the abandoned pages in the arena -- this waits for any readers to finish
_mi_arena_page_unabandon(page); _mi_arena_page_unabandon(page);
_mi_heap_page_reclaim(tagheap, page); _mi_heap_page_reclaim(tagheap, page);
_mi_stat_counter_increase(&_mi_stats_main.pages_reclaim_on_free, 1); mi_heap_stat_counter_increase(tagheap, pages_reclaim_on_free, 1);
return; return;
} }
} }

View file

@ -143,7 +143,7 @@ static void mi_heap_collect_ex(mi_heap_t* heap, mi_collect_t collect)
// collect all pages owned by this thread // collect all pages owned by this thread
mi_heap_visit_pages(heap, &mi_heap_page_collect, &collect, NULL); mi_heap_visit_pages(heap, &mi_heap_page_collect, &collect, NULL);
// collect arenas (this is program wide so don't force purges on abandonment of threads) // collect arenas (this is program wide so don't force purges on abandonment of threads)
_mi_arenas_collect(collect == MI_FORCE /* force purge? */); _mi_arenas_collect(collect == MI_FORCE /* force purge? */);
} }
@ -184,20 +184,19 @@ mi_heap_t* mi_heap_get_backing(void) {
mi_assert_internal(heap!=NULL); mi_assert_internal(heap!=NULL);
mi_heap_t* bheap = heap->tld->heap_backing; mi_heap_t* bheap = heap->tld->heap_backing;
mi_assert_internal(bheap!=NULL); mi_assert_internal(bheap!=NULL);
mi_assert_internal(bheap->thread_id == _mi_thread_id()); mi_assert_internal(bheap->tld->thread_id == _mi_thread_id());
return bheap; return bheap;
} }
// todo: make order of parameters consistent (but would that break compat with CPython?) // todo: make order of parameters consistent (but would that break compat with CPython?)
void _mi_heap_init(mi_heap_t* heap, mi_arena_id_t arena_id, bool noreclaim, uint8_t heap_tag, mi_tld_t* tld) void _mi_heap_init(mi_heap_t* heap, mi_arena_id_t arena_id, bool noreclaim, uint8_t heap_tag, mi_tld_t* tld)
{ {
mi_assert_internal(heap!=NULL); mi_assert_internal(heap!=NULL);
mi_memid_t memid = heap->memid; mi_memid_t memid = heap->memid;
_mi_memcpy_aligned(heap, &_mi_heap_empty, sizeof(mi_heap_t)); _mi_memcpy_aligned(heap, &_mi_heap_empty, sizeof(mi_heap_t));
heap->memid = memid; heap->memid = memid;
heap->tld = tld; // avoid reading the thread-local tld during initialization heap->tld = tld; // avoid reading the thread-local tld during initialization
heap->thread_id = _mi_thread_id(); heap->exclusive_arena = _mi_arena_from_id(arena_id);
heap->arena_id = arena_id;
heap->allow_page_reclaim = !noreclaim; heap->allow_page_reclaim = !noreclaim;
heap->allow_page_abandon = (!noreclaim && mi_option_get(mi_option_full_page_retain) >= 0); heap->allow_page_abandon = (!noreclaim && mi_option_get(mi_option_full_page_retain) >= 0);
heap->full_page_retain = mi_option_get_clamp(mi_option_full_page_retain, -1, 32); heap->full_page_retain = mi_option_get_clamp(mi_option_full_page_retain, -1, 32);
@ -211,7 +210,7 @@ void _mi_heap_init(mi_heap_t* heap, mi_arena_id_t arena_id, bool noreclaim, uint
heap->full_page_retain = heap->full_page_retain / 4; heap->full_page_retain = heap->full_page_retain / 4;
} }
} }
if (heap->tld->heap_backing == NULL) { if (heap->tld->heap_backing == NULL) {
heap->tld->heap_backing = heap; // first heap becomes the backing heap heap->tld->heap_backing = heap; // first heap becomes the backing heap
_mi_random_init(&heap->random); _mi_random_init(&heap->random);
@ -220,8 +219,8 @@ void _mi_heap_init(mi_heap_t* heap, mi_arena_id_t arena_id, bool noreclaim, uint
_mi_random_split(&heap->tld->heap_backing->random, &heap->random); _mi_random_split(&heap->tld->heap_backing->random, &heap->random);
} }
heap->cookie = _mi_heap_random_next(heap) | 1; heap->cookie = _mi_heap_random_next(heap) | 1;
heap->keys[0] = _mi_heap_random_next(heap); //heap->keys[0] = _mi_heap_random_next(heap);
heap->keys[1] = _mi_heap_random_next(heap); //heap->keys[1] = _mi_heap_random_next(heap);*/
_mi_heap_guarded_init(heap); _mi_heap_guarded_init(heap);
// push on the thread local heaps list // push on the thread local heaps list
@ -234,7 +233,15 @@ mi_heap_t* _mi_heap_create(int heap_tag, bool allow_destroy, mi_arena_id_t arena
mi_assert(heap_tag >= 0 && heap_tag < 256); mi_assert(heap_tag >= 0 && heap_tag < 256);
// allocate and initialize a heap // allocate and initialize a heap
mi_memid_t memid; mi_memid_t memid;
mi_heap_t* heap = (mi_heap_t*)_mi_meta_zalloc(sizeof(mi_heap_t), &memid); mi_heap_t* heap;
if (arena_id == _mi_arena_id_none()) {
heap = (mi_heap_t*)_mi_meta_zalloc(sizeof(mi_heap_t), &memid);
}
else {
// heaps associated wita a specific arena are allocated in that arena
// note: takes up at least one slice which is quite wasteful...
heap = (mi_heap_t*)_mi_arena_alloc(_mi_subproc(), _mi_align_up(sizeof(mi_heap_t),MI_ARENA_MIN_OBJ_SIZE), true, true, _mi_arena_from_id(arena_id), tld->thread_seq, &memid);
}
if (heap==NULL) { if (heap==NULL) {
_mi_error_message(ENOMEM, "unable to allocate heap meta-data\n"); _mi_error_message(ENOMEM, "unable to allocate heap meta-data\n");
return NULL; return NULL;
@ -247,7 +254,7 @@ mi_heap_t* _mi_heap_create(int heap_tag, bool allow_destroy, mi_arena_id_t arena
mi_decl_nodiscard mi_heap_t* mi_heap_new_ex(int heap_tag, bool allow_destroy, mi_arena_id_t arena_id) { mi_decl_nodiscard mi_heap_t* mi_heap_new_ex(int heap_tag, bool allow_destroy, mi_arena_id_t arena_id) {
mi_heap_t* bheap = mi_heap_get_backing(); mi_heap_t* bheap = mi_heap_get_backing();
mi_assert_internal(bheap != NULL); mi_assert_internal(bheap != NULL);
return _mi_heap_create(heap_tag, allow_destroy, arena_id, bheap->tld); return _mi_heap_create(heap_tag, allow_destroy, arena_id, bheap->tld);
} }
mi_decl_nodiscard mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t arena_id) { mi_decl_nodiscard mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t arena_id) {
@ -260,7 +267,7 @@ mi_decl_nodiscard mi_heap_t* mi_heap_new(void) {
} }
bool _mi_heap_memid_is_suitable(mi_heap_t* heap, mi_memid_t memid) { bool _mi_heap_memid_is_suitable(mi_heap_t* heap, mi_memid_t memid) {
return _mi_arena_memid_is_suitable(memid, heap->arena_id); return _mi_arena_memid_is_suitable(memid, heap->exclusive_arena);
} }
uintptr_t _mi_heap_random_next(mi_heap_t* heap) { uintptr_t _mi_heap_random_next(mi_heap_t* heap) {
@ -279,7 +286,7 @@ static void mi_heap_reset_pages(mi_heap_t* heap) {
} }
// called from `mi_heap_destroy` and `mi_heap_delete` to free the internal heap resources. // called from `mi_heap_destroy` and `mi_heap_delete` to free the internal heap resources.
static void mi_heap_free(mi_heap_t* heap) { static void mi_heap_free(mi_heap_t* heap, bool do_free_mem) {
mi_assert(heap != NULL); mi_assert(heap != NULL);
mi_assert_internal(mi_heap_is_initialized(heap)); mi_assert_internal(mi_heap_is_initialized(heap));
if (heap==NULL || !mi_heap_is_initialized(heap)) return; if (heap==NULL || !mi_heap_is_initialized(heap)) return;
@ -306,7 +313,9 @@ static void mi_heap_free(mi_heap_t* heap) {
mi_assert_internal(heap->tld->heaps != NULL); mi_assert_internal(heap->tld->heaps != NULL);
// and free the used memory // and free the used memory
_mi_meta_free(heap, sizeof(*heap), heap->memid); if (do_free_mem) {
_mi_meta_free(heap, sizeof(*heap), heap->memid);
}
} }
// return a heap on the same thread as `heap` specialized for the specified tag (if it exists) // return a heap on the same thread as `heap` specialized for the specified tag (if it exists)
@ -340,17 +349,17 @@ static bool _mi_heap_page_destroy(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_
if (bsize > MI_LARGE_MAX_OBJ_SIZE) { if (bsize > MI_LARGE_MAX_OBJ_SIZE) {
mi_heap_stat_decrease(heap, huge, bsize); mi_heap_stat_decrease(heap, huge, bsize);
} }
#if (MI_STAT) #if (MI_STAT)
_mi_page_free_collect(page, false); // update used count _mi_page_free_collect(page, false); // update used count
const size_t inuse = page->used; const size_t inuse = page->used;
if (bsize <= MI_LARGE_MAX_OBJ_SIZE) { if (bsize <= MI_LARGE_MAX_OBJ_SIZE) {
mi_heap_stat_decrease(heap, normal, bsize * inuse); mi_heap_stat_decrease(heap, normal, bsize * inuse);
#if (MI_STAT>1) #if (MI_STAT>1)
mi_heap_stat_decrease(heap, normal_bins[_mi_bin(bsize)], inuse); mi_heap_stat_decrease(heap, normal_bins[_mi_bin(bsize)], inuse);
#endif #endif
} }
mi_heap_stat_decrease(heap, malloc, bsize * inuse); // todo: off for aligned blocks... mi_heap_stat_decrease(heap, malloc, bsize * inuse); // todo: off for aligned blocks...
#endif #endif
/// pretend it is all free now /// pretend it is all free now
mi_assert_internal(mi_page_thread_free(page) == NULL); mi_assert_internal(mi_page_thread_free(page) == NULL);
@ -402,7 +411,7 @@ void mi_heap_destroy(mi_heap_t* heap) {
#endif #endif
// free all pages // free all pages
_mi_heap_destroy_pages(heap); _mi_heap_destroy_pages(heap);
mi_heap_free(heap); mi_heap_free(heap,true);
} }
#endif #endif
} }
@ -461,20 +470,11 @@ void mi_heap_delete(mi_heap_t* heap)
mi_assert_expensive(mi_heap_is_valid(heap)); mi_assert_expensive(mi_heap_is_valid(heap));
if (heap==NULL || !mi_heap_is_initialized(heap)) return; if (heap==NULL || !mi_heap_is_initialized(heap)) return;
/* // abandon all pages
mi_heap_t* bheap = heap->tld->heap_backing; _mi_heap_collect_abandon(heap);
if (bheap != heap && mi_heaps_are_compatible(bheap,heap)) {
// transfer still used pages to the backing heap
mi_heap_absorb(bheap, heap);
}
else
*/
{
// abandon all pages
_mi_heap_collect_abandon(heap);
}
mi_assert_internal(heap->page_count==0); mi_assert_internal(heap->page_count==0);
mi_heap_free(heap); mi_heap_free(heap,true);
} }
mi_heap_t* mi_heap_set_default(mi_heap_t* heap) { mi_heap_t* mi_heap_set_default(mi_heap_t* heap) {
@ -488,7 +488,63 @@ mi_heap_t* mi_heap_set_default(mi_heap_t* heap) {
} }
/* -----------------------------------------------------------
Load/unload heaps
----------------------------------------------------------- */
void mi_heap_unload(mi_heap_t* heap) {
mi_assert(mi_heap_is_initialized(heap));
mi_assert_expensive(mi_heap_is_valid(heap));
if (heap==NULL || !mi_heap_is_initialized(heap)) return;
if (heap->exclusive_arena == NULL) {
_mi_warning_message("cannot unload heaps that are not associated with an exclusive arena\n");
return;
}
// abandon all pages so all thread'id in the pages are cleared
_mi_heap_collect_abandon(heap);
mi_assert_internal(heap->page_count==0);
// remove from heap list
mi_heap_free(heap, false /* but don't actually free the memory */);
// disassociate from the current thread-local and static state
heap->tld = NULL;
return;
}
bool mi_heap_reload(mi_heap_t* heap, mi_arena_id_t arena_id) {
mi_assert(mi_heap_is_initialized(heap));
if (heap==NULL || !mi_heap_is_initialized(heap)) return false;
if (heap->exclusive_arena == NULL) {
_mi_warning_message("cannot reload heaps that were not associated with an exclusive arena\n");
return false;
}
if (heap->tld != NULL) {
_mi_warning_message("cannot reload heaps that were not unloaded first\n");
return false;
}
mi_arena_t* arena = _mi_arena_from_id(arena_id);
if (heap->exclusive_arena != arena) {
_mi_warning_message("trying to reload a heap at a different arena address: %p vs %p\n", heap->exclusive_arena, arena);
return false;
}
mi_assert_internal(heap->page_count==0);
// re-associate from the current thread-local and static state
heap->tld = _mi_tld();
// reinit direct pages (as we may be in a different process)
mi_assert_internal(heap->page_count == 0);
for (int i = 0; i < MI_PAGES_DIRECT; i++) {
heap->pages_free_direct[i] = (mi_page_t*)&_mi_page_empty;
}
// push on the thread local heaps list
heap->next = heap->tld->heaps;
heap->tld->heaps = heap;
return true;
}
/* ----------------------------------------------------------- /* -----------------------------------------------------------
Analysis Analysis

View file

@ -11,31 +11,31 @@ terms of the MIT license. A copy of the license can be found in the file
#include <string.h> // memcpy, memset #include <string.h> // memcpy, memset
#include <stdlib.h> // atexit #include <stdlib.h> // atexit
#define MI_MEMID_STATIC {{{NULL,0}}, MI_MEM_STATIC, true /* pinned */, true /* committed */, false /* zero */ } #define MI_MEMID_INIT(kind) {{{NULL,0}}, kind, true /* pinned */, true /* committed */, false /* zero */ }
#define MI_MEMID_STATIC MI_MEMID_INIT(MI_MEM_STATIC)
// Empty page used to initialize the small free pages array // Empty page used to initialize the small free pages array
const mi_page_t _mi_page_empty = { const mi_page_t _mi_page_empty = {
MI_ATOMIC_VAR_INIT(0), // xthread_id MI_ATOMIC_VAR_INIT(0), // xthread_id
NULL, // free NULL, // free
0, // used 0, // used
0, // capacity 0, // capacity
0, // reserved capacity 0, // reserved capacity
0, // block size shift 0, // block size shift
0, // retire_expire 0, // retire_expire
NULL, // local_free NULL, // local_free
MI_ATOMIC_VAR_INIT(0), // xthread_free MI_ATOMIC_VAR_INIT(0), // xthread_free
MI_ATOMIC_VAR_INIT(0), // xflags MI_ATOMIC_VAR_INIT(0), // xflags
0, // block_size 0, // block_size
NULL, // page_start NULL, // page_start
0, // heap tag 0, // heap tag
false, // is_zero false, // is_zero
#if (MI_PADDING || MI_ENCODE_FREELIST) #if (MI_PADDING || MI_ENCODE_FREELIST)
{ 0, 0 }, { 0, 0 }, // keys
#endif #endif
NULL, // xheap NULL, // xheap
NULL, NULL, // next, prev NULL, NULL, // next, prev
NULL, // subproc MI_MEMID_STATIC // memid
MI_MEMID_STATIC // memid
}; };
#define MI_PAGE_EMPTY() ((mi_page_t*)&_mi_page_empty) #define MI_PAGE_EMPTY() ((mi_page_t*)&_mi_page_empty)
@ -96,28 +96,76 @@ const mi_page_t _mi_page_empty = {
// may lead to allocation itself on some platforms) // may lead to allocation itself on some platforms)
// -------------------------------------------------------- // --------------------------------------------------------
static mi_decl_cache_align mi_subproc_t subproc_main;
static mi_decl_cache_align mi_tld_t tld_empty = {
0, // thread_id
0, // thread_seq
&subproc_main, // subproc
NULL, // heap_backing
NULL, // heaps list
0, // heartbeat
false, // recurse
false, // is_in_threadpool
{ MI_STATS_NULL }, // stats
MI_MEMID_STATIC // memid
};
mi_decl_cache_align const mi_heap_t _mi_heap_empty = { mi_decl_cache_align const mi_heap_t _mi_heap_empty = {
NULL, &tld_empty, // tld
// MI_ATOMIC_VAR_INIT(NULL), // thread delayed free NULL, // exclusive_arena
0, // thread_id 0, // cookie
0, // arena_id //{ 0, 0 }, // keys
0, // cookie { {0}, {0}, 0, true }, // random
{ 0, 0 }, // keys 0, // page count
{ {0}, {0}, 0, true }, // random MI_BIN_FULL, 0, // page retired min/max
0, // page count NULL, // next
MI_BIN_FULL, 0, // page retired min/max 0, // full page retain
NULL, // next false, // can reclaim
MI_MEMID_STATIC, // memid true, // can eager abandon
0, 0, // tag
0, // full page retain
false, // can reclaim
true, // can eager abandon
0, // tag
#if MI_GUARDED #if MI_GUARDED
0, 0, 0, 0, 1, // count is 1 so we never write to it (see `internal.h:mi_heap_malloc_use_guarded`) 0, 0, 0, 0, 1, // count is 1 so we never write to it (see `internal.h:mi_heap_malloc_use_guarded`)
#endif #endif
MI_SMALL_PAGES_EMPTY, MI_SMALL_PAGES_EMPTY,
MI_PAGE_QUEUES_EMPTY MI_PAGE_QUEUES_EMPTY,
MI_MEMID_STATIC
};
extern mi_heap_t heap_main;
static mi_decl_cache_align mi_tld_t tld_main = {
0, // thread_id
0, // thread_seq
&subproc_main, // subproc
&heap_main, // heap_backing
&heap_main, // heaps list
0, // heartbeat
false, // recurse
false, // is_in_threadpool
{ MI_STATS_NULL }, // stats
MI_MEMID_STATIC // memid
};
mi_decl_cache_align mi_heap_t heap_main = {
&tld_main, // thread local data
NULL, // exclusive arena
0, // initial cookie
//{ 0, 0 }, // the key of the main heap can be fixed (unlike page keys that need to be secure!)
{ {0x846ca68b}, {0}, 0, true }, // random
0, // page count
MI_BIN_FULL, 0, // page retired min/max
NULL, // next heap
2, // full page retain
true, // allow page reclaim
true, // allow page abandon
0, // tag
#if MI_GUARDED
0, 0, 0, 0, 0,
#endif
MI_SMALL_PAGES_EMPTY,
MI_PAGE_QUEUES_EMPTY,
MI_MEMID_STATIC
}; };
@ -125,49 +173,9 @@ mi_threadid_t _mi_thread_id(void) mi_attr_noexcept {
return _mi_prim_thread_id(); return _mi_prim_thread_id();
} }
// the thread-local default heap for allocation // the thread-local default heap for allocation
mi_decl_thread mi_heap_t* _mi_heap_default = (mi_heap_t*)&_mi_heap_empty; mi_decl_thread mi_heap_t* _mi_heap_default = (mi_heap_t*)&_mi_heap_empty;
extern mi_heap_t _mi_heap_main;
static mi_decl_cache_align mi_subproc_t mi_subproc_default;
static mi_decl_cache_align mi_tld_t tld_main = {
0,
&_mi_heap_main, // heap_backing
&_mi_heap_main, // heaps list
&mi_subproc_default, // subproc
0, // tseq
MI_MEMID_STATIC, // memid
false, // recurse
false, // is_in_threadpool
{ MI_STATS_NULL } // stats
};
mi_decl_cache_align mi_heap_t _mi_heap_main = {
&tld_main,
// MI_ATOMIC_VAR_INIT(NULL), // thread delayed free list
0, // thread id
0, // initial cookie
0, // arena id
{ 0, 0 }, // the key of the main heap can be fixed (unlike page keys that need to be secure!)
{ {0x846ca68b}, {0}, 0, true }, // random
0, // page count
MI_BIN_FULL, 0, // page retired min/max
NULL, // next heap
MI_MEMID_STATIC, // memid
0,
2, // full page retain
true, // allow page reclaim
true, // allow page abandon
0, // tag
#if MI_GUARDED
0, 0, 0, 0, 0,
#endif
MI_SMALL_PAGES_EMPTY,
MI_PAGE_QUEUES_EMPTY
};
bool _mi_process_is_initialized = false; // set to `true` in `mi_process_init`. bool _mi_process_is_initialized = false; // set to `true` in `mi_process_init`.
@ -212,30 +220,46 @@ void _mi_heap_guarded_init(mi_heap_t* heap) {
} }
#endif #endif
// Initialize main subproc
static void mi_heap_main_init(void) { static void mi_subproc_main_init(void) {
if (_mi_heap_main.cookie == 0) { if (subproc_main.memid.memkind != MI_MEM_STATIC) {
_mi_heap_main.thread_id = _mi_thread_id(); subproc_main.memid = _mi_memid_create(MI_MEM_STATIC);
_mi_heap_main.cookie = 1; mi_lock_init(&subproc_main.os_abandoned_pages_lock);
#if defined(__APPLE__) || defined(_WIN32) && !defined(MI_SHARED_LIB) mi_lock_init(&subproc_main.arena_reserve_lock);
_mi_random_init_weak(&_mi_heap_main.random); // prevent allocation failure during bcrypt dll initialization with static linking
#else
_mi_random_init(&_mi_heap_main.random);
#endif
_mi_heap_main.cookie = _mi_heap_random_next(&_mi_heap_main);
_mi_heap_main.keys[0] = _mi_heap_random_next(&_mi_heap_main);
_mi_heap_main.keys[1] = _mi_heap_random_next(&_mi_heap_main);
mi_lock_init(&mi_subproc_default.abandoned_os_lock);
mi_lock_init(&mi_subproc_default.abandoned_os_visit_lock);
_mi_heap_guarded_init(&_mi_heap_main);
_mi_heap_main.allow_page_abandon = (mi_option_get(mi_option_full_page_retain) >= 0);
_mi_heap_main.full_page_retain = mi_option_get_clamp(mi_option_full_page_retain, -1, 32);
} }
} }
mi_heap_t* _mi_heap_main_get(void) { // Initialize main tld
static void mi_tld_main_init(void) {
if (tld_main.thread_id == 0) {
tld_main.thread_id = _mi_prim_thread_id();
}
}
// Initialization of the (statically allocated) main heap, and the main tld and subproc.
static void mi_heap_main_init(void) {
if (heap_main.cookie == 0) {
mi_subproc_main_init();
mi_tld_main_init();
// heap
heap_main.cookie = 1;
#if defined(__APPLE__) || defined(_WIN32) && !defined(MI_SHARED_LIB)
_mi_random_init_weak(&heap_main.random); // prevent allocation failure during bcrypt dll initialization with static linking
#else
_mi_random_init(&heap_main.random);
#endif
heap_main.cookie = _mi_heap_random_next(&heap_main);
//heap_main.keys[0] = _mi_heap_random_next(&heap_main);
//heap_main.keys[1] = _mi_heap_random_next(&heap_main);
_mi_heap_guarded_init(&heap_main);
heap_main.allow_page_abandon = (mi_option_get(mi_option_full_page_retain) >= 0);
heap_main.full_page_retain = mi_option_get_clamp(mi_option_full_page_retain, -1, 32);
}
}
mi_heap_t* heap_main_get(void) {
mi_heap_main_init(); mi_heap_main_init();
return &_mi_heap_main; return &heap_main;
} }
@ -243,14 +267,21 @@ mi_heap_t* _mi_heap_main_get(void) {
Thread local data Thread local data
----------------------------------------------------------- */ ----------------------------------------------------------- */
// Thread sequence number // Count current and total created threads
static _Atomic(size_t) mi_tcount; static _Atomic(size_t) thread_count = MI_ATOMIC_VAR_INIT(1);
static _Atomic(size_t) thread_total_count;
size_t _mi_current_thread_count(void) {
return mi_atomic_load_relaxed(&thread_count);
}
// The mimalloc thread local data // The mimalloc thread local data
mi_decl_thread mi_tld_t* mi_tld; mi_decl_thread mi_tld_t* thread_tld = &tld_empty;
// Allocate fresh tld // Allocate fresh tld
static mi_tld_t* mi_tld_alloc(void) { static mi_tld_t* mi_tld_alloc(void) {
mi_atomic_increment_relaxed(&thread_count);
if (_mi_is_main_thread()) { if (_mi_is_main_thread()) {
return &tld_main; return &tld_main;
} }
@ -267,8 +298,9 @@ static mi_tld_t* mi_tld_alloc(void) {
tld->memid = memid; tld->memid = memid;
tld->heap_backing = NULL; tld->heap_backing = NULL;
tld->heaps = NULL; tld->heaps = NULL;
tld->subproc = &mi_subproc_default; tld->subproc = &subproc_main;
tld->tseq = mi_atomic_add_acq_rel(&mi_tcount, 1); tld->thread_id = _mi_prim_thread_id();
tld->thread_seq = mi_atomic_add_acq_rel(&thread_total_count, 1);
tld->is_in_threadpool = _mi_prim_thread_is_in_threadpool(); tld->is_in_threadpool = _mi_prim_thread_is_in_threadpool();
return tld; return tld;
} }
@ -278,27 +310,49 @@ static mi_tld_t* mi_tld_alloc(void) {
mi_decl_noinline static void mi_tld_free(void) { mi_decl_noinline static void mi_tld_free(void) {
mi_tld_t* tld = _mi_tld(); mi_tld_t* tld = _mi_tld();
mi_tld = MI_TLD_INVALID; if (tld != NULL && tld != MI_TLD_INVALID) {
_mi_meta_free(tld, sizeof(mi_tld_t), tld->memid); _mi_stats_done(&tld->stats);
_mi_meta_free(tld, sizeof(mi_tld_t), tld->memid);
}
tld = MI_TLD_INVALID;
mi_atomic_decrement_relaxed(&thread_count);
} }
mi_decl_noinline mi_tld_t* _mi_tld(void) { mi_decl_noinline mi_tld_t* _mi_tld(void) {
if (mi_tld == MI_TLD_INVALID) { mi_tld_t* tld = thread_tld;
_mi_error_message(EFAULT, "internal error: tld accessed after the thread terminated\n"); if (tld == MI_TLD_INVALID) {
mi_tld = NULL; _mi_error_message(EFAULT, "internal error: tld is accessed after the thread terminated\n");
thread_tld = &tld_empty;
} }
if (mi_tld==NULL) { if (tld==&tld_empty) {
mi_tld = mi_tld_alloc(); thread_tld = tld = mi_tld_alloc();
} }
return mi_tld; return tld;
} }
mi_subproc_t* _mi_subproc(void) {
// should work without doing initialization (as it may be called from `_mi_tld -> mi_tld_alloc ... -> os_alloc -> _mi_subproc()`
// todo: this will still fail on OS systems where the first access to a thread-local causes allocation.
// on such systems we can check for this with the _mi_prim_get_default_heap as those are protected (by being
// stored in a TLS slot for example)
mi_heap_t* heap = mi_prim_get_default_heap();
if (heap == NULL || heap == &_mi_heap_empty) {
return _mi_subproc_main();
}
else {
return thread_tld->subproc; // don't call `_mi_tld()`
}
}
/* ----------------------------------------------------------- /* -----------------------------------------------------------
Sub process Sub process
----------------------------------------------------------- */ ----------------------------------------------------------- */
mi_subproc_t* _mi_subproc_main(void) {
return &subproc_main;
}
mi_subproc_id_t mi_subproc_main(void) { mi_subproc_id_t mi_subproc_main(void) {
return NULL; return NULL;
} }
@ -307,42 +361,44 @@ mi_subproc_id_t mi_subproc_new(void) {
mi_memid_t memid; mi_memid_t memid;
mi_subproc_t* subproc = (mi_subproc_t*)_mi_meta_zalloc(sizeof(mi_subproc_t),&memid); mi_subproc_t* subproc = (mi_subproc_t*)_mi_meta_zalloc(sizeof(mi_subproc_t),&memid);
if (subproc == NULL) return NULL; if (subproc == NULL) return NULL;
subproc->abandoned_os_list = NULL;
subproc->memid = memid; subproc->memid = memid;
mi_lock_init(&subproc->abandoned_os_lock); mi_lock_init(&subproc->os_abandoned_pages_lock);
mi_lock_init(&subproc->abandoned_os_visit_lock); mi_lock_init(&subproc->arena_reserve_lock);
return subproc; return subproc;
} }
mi_subproc_t* _mi_subproc_from_id(mi_subproc_id_t subproc_id) { mi_subproc_t* _mi_subproc_from_id(mi_subproc_id_t subproc_id) {
return (subproc_id == NULL ? &mi_subproc_default : (mi_subproc_t*)subproc_id); return (subproc_id == NULL ? &subproc_main : (mi_subproc_t*)subproc_id);
} }
void mi_subproc_delete(mi_subproc_id_t subproc_id) { void mi_subproc_delete(mi_subproc_id_t subproc_id) {
if (subproc_id == NULL) return; if (subproc_id == NULL) return;
mi_subproc_t* subproc = _mi_subproc_from_id(subproc_id); mi_subproc_t* subproc = _mi_subproc_from_id(subproc_id);
// check if there are no abandoned segments still.. // check if there are os pages still..
bool safe_to_delete = false; bool safe_to_delete = false;
if (mi_lock_acquire(&subproc->abandoned_os_lock)) { mi_lock(&subproc->os_abandoned_pages_lock) {
if (subproc->abandoned_os_list == NULL) { if (subproc->os_abandoned_pages == NULL) {
safe_to_delete = true; safe_to_delete = true;
} }
mi_lock_release(&subproc->abandoned_os_lock);
} }
if (!safe_to_delete) return; if (!safe_to_delete) return;
// merge stats back into the main subproc?
_mi_stats_merge_from(&_mi_subproc_main()->stats, &subproc->stats);
// safe to release // safe to release
// todo: should we refcount subprocesses? // todo: should we refcount subprocesses?
mi_lock_done(&subproc->abandoned_os_lock); mi_lock_done(&subproc->os_abandoned_pages_lock);
mi_lock_done(&subproc->abandoned_os_visit_lock); mi_lock_done(&subproc->arena_reserve_lock);
_mi_meta_free(subproc, sizeof(mi_subproc_t), subproc->memid); _mi_meta_free(subproc, sizeof(mi_subproc_t), subproc->memid);
} }
void mi_subproc_add_current_thread(mi_subproc_id_t subproc_id) { void mi_subproc_add_current_thread(mi_subproc_id_t subproc_id) {
mi_heap_t* heap = mi_heap_get_default(); mi_tld_t* tld = _mi_tld();
if (heap == NULL) return; if (tld == NULL) return;
mi_assert(heap->tld->subproc == &mi_subproc_default); mi_assert(tld->subproc == &subproc_main);
if (heap->tld->subproc != &mi_subproc_default) return; if (tld->subproc != &subproc_main) return;
heap->tld->subproc = _mi_subproc_from_id(subproc_id); tld->subproc = _mi_subproc_from_id(subproc_id);
} }
@ -354,10 +410,10 @@ void mi_subproc_add_current_thread(mi_subproc_id_t subproc_id) {
static bool _mi_thread_heap_init(void) { static bool _mi_thread_heap_init(void) {
if (mi_heap_is_initialized(mi_prim_get_default_heap())) return true; if (mi_heap_is_initialized(mi_prim_get_default_heap())) return true;
if (_mi_is_main_thread()) { if (_mi_is_main_thread()) {
// mi_assert_internal(_mi_heap_main.thread_id != 0); // can happen on freeBSD where alloc is called before any initialization // mi_assert_internal(heap_main.thread_id != 0); // can happen on freeBSD where alloc is called before any initialization
// the main heap is statically allocated // the main heap is statically allocated
mi_heap_main_init(); mi_heap_main_init();
_mi_heap_set_default_direct(&_mi_heap_main); _mi_heap_set_default_direct(&heap_main);
//mi_assert_internal(_mi_heap_default->tld->heap_backing == mi_prim_get_default_heap()); //mi_assert_internal(_mi_heap_default->tld->heap_backing == mi_prim_get_default_heap());
} }
else { else {
@ -374,7 +430,7 @@ static bool _mi_thread_heap_init(void) {
_mi_heap_set_default_direct(heap); _mi_heap_set_default_direct(heap);
// now that the heap is set for this thread, we can set the thread-local tld. // now that the heap is set for this thread, we can set the thread-local tld.
mi_tld = tld; thread_tld = tld;
} }
return false; return false;
} }
@ -385,7 +441,7 @@ static bool _mi_thread_heap_done(mi_heap_t* heap) {
if (!mi_heap_is_initialized(heap)) return true; if (!mi_heap_is_initialized(heap)) return true;
// reset default heap // reset default heap
_mi_heap_set_default_direct(_mi_is_main_thread() ? &_mi_heap_main : (mi_heap_t*)&_mi_heap_empty); _mi_heap_set_default_direct(_mi_is_main_thread() ? &heap_main : (mi_heap_t*)&_mi_heap_empty);
// switch to backing heap // switch to backing heap
heap = heap->tld->heap_backing; heap = heap->tld->heap_backing;
@ -405,22 +461,19 @@ static bool _mi_thread_heap_done(mi_heap_t* heap) {
mi_assert_internal(mi_heap_is_backing(heap)); mi_assert_internal(mi_heap_is_backing(heap));
// collect if not the main thread // collect if not the main thread
if (heap != &_mi_heap_main) { if (heap != &heap_main) {
_mi_heap_collect_abandon(heap); _mi_heap_collect_abandon(heap);
} }
// merge stats
_mi_stats_done(&heap->tld->stats);
// free heap meta data // free heap meta data
_mi_meta_free(heap, sizeof(mi_heap_t), heap->memid); _mi_meta_free(heap, sizeof(mi_heap_t), heap->memid);
if (heap == &_mi_heap_main) { if (heap == &heap_main) {
#if 0 #if 0
// never free the main thread even in debug mode; if a dll is linked statically with mimalloc, // never free the main thread even in debug mode; if a dll is linked statically with mimalloc,
// there may still be delete/free calls after the mi_fls_done is called. Issue #207 // there may still be delete/free calls after the mi_fls_done is called. Issue #207
_mi_heap_destroy_pages(heap); _mi_heap_destroy_pages(heap);
mi_assert_internal(heap->tld->heap_backing == &_mi_heap_main); mi_assert_internal(heap->tld->heap_backing == &heap_main);
#endif #endif
} }
@ -451,19 +504,14 @@ static void mi_process_setup_auto_thread_done(void) {
if (tls_initialized) return; if (tls_initialized) return;
tls_initialized = true; tls_initialized = true;
_mi_prim_thread_init_auto_done(); _mi_prim_thread_init_auto_done();
_mi_heap_set_default_direct(&_mi_heap_main); _mi_heap_set_default_direct(&heap_main);
} }
bool _mi_is_main_thread(void) { bool _mi_is_main_thread(void) {
return (_mi_heap_main.thread_id==0 || _mi_heap_main.thread_id == _mi_thread_id()); return (tld_main.thread_id==0 || tld_main.thread_id == _mi_thread_id());
} }
static _Atomic(size_t) thread_count = MI_ATOMIC_VAR_INIT(1);
size_t _mi_current_thread_count(void) {
return mi_atomic_load_relaxed(&thread_count);
}
// This is called from the `mi_malloc_generic` // This is called from the `mi_malloc_generic`
void mi_thread_init(void) mi_attr_noexcept void mi_thread_init(void) mi_attr_noexcept
@ -476,8 +524,7 @@ void mi_thread_init(void) mi_attr_noexcept
// fiber/pthread key to a non-zero value, ensuring `_mi_thread_done` is called) // fiber/pthread key to a non-zero value, ensuring `_mi_thread_done` is called)
if (_mi_thread_heap_init()) return; // returns true if already initialized if (_mi_thread_heap_init()) return; // returns true if already initialized
_mi_stat_increase(&_mi_stats_main.threads, 1); mi_subproc_stat_increase(_mi_subproc_main(), threads, 1);
mi_atomic_increment_relaxed(&thread_count);
//_mi_verbose_message("thread init: 0x%zx\n", _mi_thread_id()); //_mi_verbose_message("thread init: 0x%zx\n", _mi_thread_id());
} }
@ -499,11 +546,10 @@ void _mi_thread_done(mi_heap_t* heap)
} }
// adjust stats // adjust stats
mi_atomic_decrement_relaxed(&thread_count); mi_subproc_stat_decrease(_mi_subproc_main(), threads, 1);
_mi_stat_decrease(&_mi_stats_main.threads, 1);
// check thread-id as on Windows shutdown with FLS the main (exit) thread may call this on thread-local heaps... // check thread-id as on Windows shutdown with FLS the main (exit) thread may call this on thread-local heaps...
if (heap->thread_id != _mi_thread_id()) return; if (heap->tld->thread_id != _mi_prim_thread_id()) return;
// abandon the thread local heap // abandon the thread local heap
_mi_thread_heap_done(heap); // returns true if already ran _mi_thread_heap_done(heap); // returns true if already ran
@ -562,7 +608,7 @@ void _mi_process_load(void) {
} }
// reseed random // reseed random
_mi_random_reinit_if_weak(&_mi_heap_main.random); _mi_random_reinit_if_weak(&heap_main.random);
} }
#if defined(_WIN32) && (defined(_M_IX86) || defined(_M_X64)) #if defined(_WIN32) && (defined(_M_IX86) || defined(_M_X64))
@ -589,7 +635,7 @@ void mi_process_init(void) mi_attr_noexcept {
// ensure we are called once // ensure we are called once
static mi_atomic_once_t process_init; static mi_atomic_once_t process_init;
#if _MSC_VER < 1920 #if _MSC_VER < 1920
mi_heap_main_init(); // vs2017 can dynamically re-initialize _mi_heap_main mi_heap_main_init(); // vs2017 can dynamically re-initialize heap_main
#endif #endif
if (!mi_atomic_once(&process_init)) return; if (!mi_atomic_once(&process_init)) return;
_mi_process_is_initialized = true; _mi_process_is_initialized = true;
@ -597,10 +643,11 @@ void mi_process_init(void) mi_attr_noexcept {
mi_process_setup_auto_thread_done(); mi_process_setup_auto_thread_done();
mi_detect_cpu_features(); mi_detect_cpu_features();
mi_subproc_main_init();
mi_tld_main_init();
mi_heap_main_init();
_mi_os_init(); _mi_os_init();
_mi_page_map_init(); _mi_page_map_init();
_mi_arena_init();
mi_heap_main_init();
#if MI_DEBUG #if MI_DEBUG
_mi_verbose_message("debug level : %d\n", MI_DEBUG); _mi_verbose_message("debug level : %d\n", MI_DEBUG);
#endif #endif
@ -611,7 +658,7 @@ void mi_process_init(void) mi_attr_noexcept {
#endif #endif
mi_thread_init(); mi_thread_init();
#if defined(_WIN32) #if defined(_WIN32) && defined(MI_WIN_USE_FLS)
// On windows, when building as a static lib the FLS cleanup happens to early for the main thread. // On windows, when building as a static lib the FLS cleanup happens to early for the main thread.
// To avoid this, set the FLS value for the main thread to NULL so the fls cleanup // To avoid this, set the FLS value for the main thread to NULL so the fls cleanup
// will not call _mi_thread_done on the (still executing) main thread. See issue #508. // will not call _mi_thread_done on the (still executing) main thread. See issue #508.
@ -672,7 +719,7 @@ void mi_cdecl _mi_process_done(void) {
mi_stats_print(NULL); mi_stats_print(NULL);
} }
_mi_allocator_done(); _mi_allocator_done();
_mi_verbose_message("process done: 0x%zx\n", _mi_heap_main.thread_id); _mi_verbose_message("process done: 0x%zx\n", tld_main.thread_id);
os_preloading = true; // don't call the C runtime anymore os_preloading = true; // don't call the C runtime anymore
} }

View file

@ -114,9 +114,9 @@ static void mi_os_prim_free(void* addr, size_t size, bool still_committed) {
_mi_warning_message("unable to free OS memory (error: %d (0x%x), size: 0x%zx bytes, address: %p)\n", err, err, size, addr); _mi_warning_message("unable to free OS memory (error: %d (0x%x), size: 0x%zx bytes, address: %p)\n", err, err, size, addr);
} }
if (still_committed) { if (still_committed) {
_mi_stat_decrease(&os_stats->committed, size); mi_os_stat_decrease(committed, size);
} }
_mi_stat_decrease(&os_stats->reserved, size); mi_os_stat_decrease(reserved, size);
} }
void _mi_os_free_ex(void* addr, size_t size, bool still_committed, mi_memid_t memid) { void _mi_os_free_ex(void* addr, size_t size, bool still_committed, mi_memid_t memid) {
@ -171,11 +171,11 @@ static void* mi_os_prim_alloc_at(void* hint_addr, size_t size, size_t try_alignm
_mi_warning_message("unable to allocate OS memory (error: %d (0x%x), addr: %p, size: 0x%zx bytes, align: 0x%zx, commit: %d, allow large: %d)\n", err, err, hint_addr, size, try_alignment, commit, allow_large); _mi_warning_message("unable to allocate OS memory (error: %d (0x%x), addr: %p, size: 0x%zx bytes, align: 0x%zx, commit: %d, allow large: %d)\n", err, err, hint_addr, size, try_alignment, commit, allow_large);
} }
_mi_stat_counter_increase(&os_stats->mmap_calls, 1); mi_os_stat_counter_increase(mmap_calls, 1);
if (p != NULL) { if (p != NULL) {
_mi_stat_increase(&os_stats->reserved, size); mi_os_stat_increase(reserved, size);
if (commit) { if (commit) {
_mi_stat_increase(&os_stats->committed, size); mi_os_stat_increase(committed, size);
// seems needed for asan (or `mimalloc-test-api` fails) // seems needed for asan (or `mimalloc-test-api` fails)
#ifdef MI_TRACK_ASAN #ifdef MI_TRACK_ASAN
if (*is_zero) { mi_track_mem_defined(p,size); } if (*is_zero) { mi_track_mem_defined(p,size); }
@ -290,7 +290,7 @@ void* _mi_os_alloc_aligned(size_t size, size_t alignment, bool commit, bool allo
if (size == 0) return NULL; if (size == 0) return NULL;
size = _mi_os_good_alloc_size(size); size = _mi_os_good_alloc_size(size);
alignment = _mi_align_up(alignment, _mi_os_page_size()); alignment = _mi_align_up(alignment, _mi_os_page_size());
bool os_is_large = false; bool os_is_large = false;
bool os_is_zero = false; bool os_is_zero = false;
void* os_base = NULL; void* os_base = NULL;
@ -379,8 +379,8 @@ static void* mi_os_page_align_area_conservative(void* addr, size_t size, size_t*
bool _mi_os_commit(void* addr, size_t size, bool* is_zero) { bool _mi_os_commit(void* addr, size_t size, bool* is_zero) {
if (is_zero != NULL) { *is_zero = false; } if (is_zero != NULL) { *is_zero = false; }
_mi_stat_increase(&os_stats->committed, size); // use size for precise commit vs. decommit mi_os_stat_increase(committed, size); // use size for precise commit vs. decommit
_mi_stat_counter_increase(&os_stats->commit_calls, 1); mi_os_stat_counter_increase(commit_calls, 1);
// page align range // page align range
size_t csize; size_t csize;
@ -408,7 +408,7 @@ bool _mi_os_commit(void* addr, size_t size, bool* is_zero) {
static bool mi_os_decommit_ex(void* addr, size_t size, bool* needs_recommit) { static bool mi_os_decommit_ex(void* addr, size_t size, bool* needs_recommit) {
mi_assert_internal(needs_recommit!=NULL); mi_assert_internal(needs_recommit!=NULL);
_mi_stat_decrease(&os_stats->committed, size); mi_os_stat_decrease(committed, size);
// page align // page align
size_t csize; size_t csize;
@ -440,8 +440,8 @@ bool _mi_os_reset(void* addr, size_t size) {
size_t csize; size_t csize;
void* start = mi_os_page_align_area_conservative(addr, size, &csize); void* start = mi_os_page_align_area_conservative(addr, size, &csize);
if (csize == 0) return true; // || _mi_os_is_huge_reserved(addr) if (csize == 0) return true; // || _mi_os_is_huge_reserved(addr)
_mi_stat_increase(&os_stats->reset, csize); mi_os_stat_increase(reset, csize);
_mi_stat_counter_increase(&os_stats->reset_calls, 1); mi_os_stat_counter_increase(reset_calls, 1);
#if (MI_DEBUG>1) && !MI_SECURE && !MI_TRACK_ENABLED // && !MI_TSAN #if (MI_DEBUG>1) && !MI_SECURE && !MI_TRACK_ENABLED // && !MI_TSAN
memset(start, 0, csize); // pretend it is eagerly reset memset(start, 0, csize); // pretend it is eagerly reset
@ -460,8 +460,8 @@ bool _mi_os_reset(void* addr, size_t size) {
bool _mi_os_purge_ex(void* p, size_t size, bool allow_reset) bool _mi_os_purge_ex(void* p, size_t size, bool allow_reset)
{ {
if (mi_option_get(mi_option_purge_delay) < 0) return false; // is purging allowed? if (mi_option_get(mi_option_purge_delay) < 0) return false; // is purging allowed?
_mi_stat_counter_increase(&os_stats->purge_calls, 1); mi_os_stat_counter_increase(purge_calls, 1);
_mi_stat_increase(&os_stats->purged, size); mi_os_stat_increase(purged, size);
if (mi_option_is_enabled(mi_option_purge_decommits) && // should decommit? if (mi_option_is_enabled(mi_option_purge_decommits) && // should decommit?
!_mi_preloading()) // don't decommit during preloading (unsafe) !_mi_preloading()) // don't decommit during preloading (unsafe)
@ -595,8 +595,8 @@ void* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_mse
// success, record it // success, record it
page++; // increase before timeout check (see issue #711) page++; // increase before timeout check (see issue #711)
_mi_stat_increase(&os_stats->committed, MI_HUGE_OS_PAGE_SIZE); mi_os_stat_increase(committed, MI_HUGE_OS_PAGE_SIZE);
_mi_stat_increase(&os_stats->reserved, MI_HUGE_OS_PAGE_SIZE); mi_os_stat_increase(reserved, MI_HUGE_OS_PAGE_SIZE);
// check for timeout // check for timeout
if (max_msecs > 0) { if (max_msecs > 0) {

View file

@ -387,9 +387,9 @@ void _mi_page_retire(mi_page_t* page) mi_attr_noexcept {
const size_t bsize = mi_page_block_size(page); const size_t bsize = mi_page_block_size(page);
if mi_likely( /* bsize < MI_MAX_RETIRE_SIZE && */ !mi_page_queue_is_special(pq)) { // not full or huge queue? if mi_likely( /* bsize < MI_MAX_RETIRE_SIZE && */ !mi_page_queue_is_special(pq)) { // not full or huge queue?
if (pq->last==page && pq->first==page) { // the only page in the queue? if (pq->last==page && pq->first==page) { // the only page in the queue?
mi_stat_counter_increase(_mi_stats_main.page_no_retire,1);
page->retire_expire = (bsize <= MI_SMALL_MAX_OBJ_SIZE ? MI_RETIRE_CYCLES : MI_RETIRE_CYCLES/4);
mi_heap_t* heap = mi_page_heap(page); mi_heap_t* heap = mi_page_heap(page);
mi_debug_heap_stat_counter_increase(heap, page_no_retire, 1);
page->retire_expire = (bsize <= MI_SMALL_MAX_OBJ_SIZE ? MI_RETIRE_CYCLES : MI_RETIRE_CYCLES/4);
mi_assert_internal(pq >= heap->pages); mi_assert_internal(pq >= heap->pages);
const size_t index = pq - heap->pages; const size_t index = pq - heap->pages;
mi_assert_internal(index < MI_BIN_FULL && index < MI_BIN_HUGE); mi_assert_internal(index < MI_BIN_FULL && index < MI_BIN_HUGE);
@ -554,7 +554,7 @@ static void mi_page_extend_free(mi_heap_t* heap, mi_page_t* page) {
size_t page_size; size_t page_size;
//uint8_t* page_start = //uint8_t* page_start =
mi_page_area(page, &page_size); mi_page_area(page, &page_size);
mi_heap_stat_counter_increase(heap, pages_extended, 1); mi_debug_heap_stat_counter_increase(heap, pages_extended, 1);
// calculate the extend count // calculate the extend count
const size_t bsize = mi_page_block_size(page); const size_t bsize = mi_page_block_size(page);
@ -583,7 +583,7 @@ static void mi_page_extend_free(mi_heap_t* heap, mi_page_t* page) {
} }
// enable the new free list // enable the new free list
page->capacity += (uint16_t)extend; page->capacity += (uint16_t)extend;
mi_heap_stat_increase(heap, page_committed, extend * bsize); mi_debug_heap_stat_increase(heap, page_committed, extend * bsize);
mi_assert_expensive(mi_page_is_valid_init(page)); mi_assert_expensive(mi_page_is_valid_init(page));
} }
@ -591,7 +591,7 @@ static void mi_page_extend_free(mi_heap_t* heap, mi_page_t* page) {
void _mi_page_init(mi_heap_t* heap, mi_page_t* page) { void _mi_page_init(mi_heap_t* heap, mi_page_t* page) {
mi_assert(page != NULL); mi_assert(page != NULL);
mi_page_set_heap(page, heap); mi_page_set_heap(page, heap);
page->subproc = heap->tld->subproc;
size_t page_size; size_t page_size;
uint8_t* page_start = mi_page_area(page, &page_size); MI_UNUSED(page_start); uint8_t* page_start = mi_page_area(page, &page_size); MI_UNUSED(page_start);
mi_track_mem_noaccess(page_start,page_size); mi_track_mem_noaccess(page_start,page_size);
@ -682,7 +682,8 @@ static mi_decl_noinline mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, m
_mi_page_free(page_candidate, pq); _mi_page_free(page_candidate, pq);
page_candidate = page; page_candidate = page;
} }
else if (page->used >= page_candidate->used && !mi_page_is_mostly_used(page)) { // prefer to reuse fuller pages (in the hope the less used page gets freed)
else if (page->used >= page_candidate->used && !mi_page_is_mostly_used(page) && !mi_page_is_expandable(page)) {
page_candidate = page; page_candidate = page;
} }
// if we find a non-expandable candidate, or searched for N pages, return with the best candidate // if we find a non-expandable candidate, or searched for N pages, return with the best candidate
@ -708,8 +709,8 @@ static mi_decl_noinline mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, m
page = next; page = next;
} // for each page } // for each page
mi_heap_stat_counter_increase(heap, searches, count); mi_debug_heap_stat_counter_increase(heap, searches, count);
// set the page to the best candidate // set the page to the best candidate
if (page_candidate != NULL) { if (page_candidate != NULL) {
page = page_candidate; page = page_candidate;

View file

@ -19,88 +19,93 @@ terms of the MIT license. A copy of the license can be found in the file
Statistics operations Statistics operations
----------------------------------------------------------- */ ----------------------------------------------------------- */
static bool mi_is_in_main(void* stat) { static void mi_stat_update_mt(mi_stat_count_t* stat, int64_t amount) {
return ((uint8_t*)stat >= (uint8_t*)&_mi_stats_main if (amount == 0) return;
&& (uint8_t*)stat < ((uint8_t*)&_mi_stats_main + sizeof(mi_stats_t))); // add atomically
int64_t current = mi_atomic_addi64_relaxed(&stat->current, amount);
mi_atomic_maxi64_relaxed(&stat->peak, current + amount);
if (amount > 0) {
mi_atomic_addi64_relaxed(&stat->allocated, amount);
}
else {
mi_atomic_addi64_relaxed(&stat->freed, -amount);
}
} }
static void mi_stat_update(mi_stat_count_t* stat, int64_t amount) { static void mi_stat_update(mi_stat_count_t* stat, int64_t amount) {
if (amount == 0) return; if (amount == 0) return;
if mi_unlikely(mi_is_in_main(stat)) // add thread local
{ stat->current += amount;
// add atomically (for abandoned pages) if (stat->current > stat->peak) stat->peak = stat->current;
int64_t current = mi_atomic_addi64_relaxed(&stat->current, amount); if (amount > 0) {
mi_atomic_maxi64_relaxed(&stat->peak, current + amount); stat->allocated += amount;
if (amount > 0) {
mi_atomic_addi64_relaxed(&stat->allocated,amount);
}
else {
mi_atomic_addi64_relaxed(&stat->freed, -amount);
}
} }
else { else {
// add thread local stat->freed += -amount;
stat->current += amount;
if (stat->current > stat->peak) stat->peak = stat->current;
if (amount > 0) {
stat->allocated += amount;
}
else {
stat->freed += -amount;
}
} }
} }
// Adjust stats to compensate; for example before committing a range, // Adjust stats to compensate; for example before committing a range,
// first adjust downwards with parts that were already committed so // first adjust downwards with parts that were already committed so
// we avoid double counting. // we avoid double counting.
static void mi_stat_adjust_mt(mi_stat_count_t* stat, int64_t amount, bool on_alloc) {
if (amount == 0) return;
// adjust atomically
mi_atomic_addi64_relaxed(&stat->current, amount);
mi_atomic_addi64_relaxed((on_alloc ? &stat->allocated : &stat->freed), amount);
}
static void mi_stat_adjust(mi_stat_count_t* stat, int64_t amount, bool on_alloc) { static void mi_stat_adjust(mi_stat_count_t* stat, int64_t amount, bool on_alloc) {
if (amount == 0) return; if (amount == 0) return;
if mi_unlikely(mi_is_in_main(stat)) stat->current += amount;
{ if (on_alloc) {
// adjust atomically stat->allocated += amount;
mi_atomic_addi64_relaxed(&stat->current, amount);
mi_atomic_addi64_relaxed((on_alloc ? &stat->allocated : &stat->freed), amount);
} }
else { else {
// don't affect the peak stat->freed += amount;
stat->current += amount;
if (on_alloc) {
stat->allocated += amount;
}
else {
stat->freed += amount;
}
} }
} }
void _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount) { void __mi_stat_counter_increase_mt(mi_stat_counter_t* stat, size_t amount) {
if (mi_is_in_main(stat)) { mi_atomic_addi64_relaxed(&stat->count, 1);
mi_atomic_addi64_relaxed( &stat->count, 1 ); mi_atomic_addi64_relaxed(&stat->total, (int64_t)amount);
mi_atomic_addi64_relaxed( &stat->total, (int64_t)amount );
}
else {
stat->count++;
stat->total += amount;
}
} }
void _mi_stat_increase(mi_stat_count_t* stat, size_t amount) { void __mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount) {
stat->count++;
stat->total += amount;
}
void __mi_stat_increase_mt(mi_stat_count_t* stat, size_t amount) {
mi_stat_update_mt(stat, (int64_t)amount);
}
void __mi_stat_increase(mi_stat_count_t* stat, size_t amount) {
mi_stat_update(stat, (int64_t)amount); mi_stat_update(stat, (int64_t)amount);
} }
void _mi_stat_decrease(mi_stat_count_t* stat, size_t amount) { void __mi_stat_decrease_mt(mi_stat_count_t* stat, size_t amount) {
mi_stat_update_mt(stat, -((int64_t)amount));
}
void __mi_stat_decrease(mi_stat_count_t* stat, size_t amount) {
mi_stat_update(stat, -((int64_t)amount)); mi_stat_update(stat, -((int64_t)amount));
} }
void _mi_stat_adjust_increase(mi_stat_count_t* stat, size_t amount, bool on_alloc) { void __mi_stat_adjust_increase_mt(mi_stat_count_t* stat, size_t amount, bool on_alloc) {
mi_stat_adjust_mt(stat, (int64_t)amount, on_alloc);
}
void __mi_stat_adjust_increase(mi_stat_count_t* stat, size_t amount, bool on_alloc) {
mi_stat_adjust(stat, (int64_t)amount, on_alloc); mi_stat_adjust(stat, (int64_t)amount, on_alloc);
} }
void _mi_stat_adjust_decrease(mi_stat_count_t* stat, size_t amount, bool on_alloc) { void __mi_stat_adjust_decrease_mt(mi_stat_count_t* stat, size_t amount, bool on_alloc) {
mi_stat_adjust_mt(stat, -((int64_t)amount), on_alloc);
}
void __mi_stat_adjust_decrease(mi_stat_count_t* stat, size_t amount, bool on_alloc) {
mi_stat_adjust(stat, -((int64_t)amount), on_alloc); mi_stat_adjust(stat, -((int64_t)amount), on_alloc);
} }
// must be thread safe as it is called from stats_merge // must be thread safe as it is called from stats_merge
static void mi_stat_add(mi_stat_count_t* stat, const mi_stat_count_t* src, int64_t unit) { static void mi_stat_add(mi_stat_count_t* stat, const mi_stat_count_t* src, int64_t unit) {
if (stat==src) return; if (stat==src) return;
@ -401,36 +406,37 @@ static void _mi_stats_print(mi_stats_t* stats, mi_output_fun* out0, void* arg0)
static mi_msecs_t mi_process_start; // = 0 static mi_msecs_t mi_process_start; // = 0
static mi_stats_t* mi_stats_get_default(void) { // return thread local stats
mi_heap_t* heap = mi_heap_get_default(); static mi_stats_t* mi_get_tld_stats(void) {
return &heap->tld->stats; return &_mi_tld()->stats;
}
static void mi_stats_merge_from(mi_stats_t* stats) {
if (stats != &_mi_stats_main) {
mi_stats_add(&_mi_stats_main, stats);
memset(stats, 0, sizeof(mi_stats_t));
}
} }
void mi_stats_reset(void) mi_attr_noexcept { void mi_stats_reset(void) mi_attr_noexcept {
mi_stats_t* stats = mi_stats_get_default(); mi_stats_t* stats = mi_get_tld_stats();
if (stats != &_mi_stats_main) { memset(stats, 0, sizeof(mi_stats_t)); } mi_subproc_t* subproc = _mi_subproc();
memset(&_mi_stats_main, 0, sizeof(mi_stats_t)); if (stats != &subproc->stats) { _mi_memzero(stats, sizeof(mi_stats_t)); }
_mi_memzero(&subproc->stats, sizeof(mi_stats_t));
if (mi_process_start == 0) { mi_process_start = _mi_clock_start(); }; if (mi_process_start == 0) { mi_process_start = _mi_clock_start(); };
} }
void mi_stats_merge(void) mi_attr_noexcept { void _mi_stats_merge_from(mi_stats_t* to, mi_stats_t* from) {
mi_stats_merge_from( mi_stats_get_default() ); if (to != from) {
mi_stats_add(to, from);
_mi_memzero(from, sizeof(mi_stats_t));
}
} }
void _mi_stats_done(mi_stats_t* stats) { // called from `mi_thread_done` void _mi_stats_done(mi_stats_t* stats) { // called from `mi_thread_done`
mi_stats_merge_from(stats); _mi_stats_merge_from(&_mi_subproc()->stats, stats);
}
void mi_stats_merge(void) mi_attr_noexcept {
_mi_stats_done( mi_get_tld_stats() );
} }
void mi_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept { void mi_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept {
mi_stats_merge_from(mi_stats_get_default()); mi_stats_merge();
_mi_stats_print(&_mi_stats_main, out, arg); _mi_stats_print(&_mi_subproc()->stats, out, arg);
} }
void mi_stats_print(void* out) mi_attr_noexcept { void mi_stats_print(void* out) mi_attr_noexcept {
@ -439,7 +445,7 @@ void mi_stats_print(void* out) mi_attr_noexcept {
} }
void mi_thread_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept { void mi_thread_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept {
_mi_stats_print(mi_stats_get_default(), out, arg); _mi_stats_print(mi_get_tld_stats(), out, arg);
} }
@ -473,11 +479,12 @@ mi_msecs_t _mi_clock_end(mi_msecs_t start) {
mi_decl_export void mi_process_info(size_t* elapsed_msecs, size_t* user_msecs, size_t* system_msecs, size_t* current_rss, size_t* peak_rss, size_t* current_commit, size_t* peak_commit, size_t* page_faults) mi_attr_noexcept mi_decl_export void mi_process_info(size_t* elapsed_msecs, size_t* user_msecs, size_t* system_msecs, size_t* current_rss, size_t* peak_rss, size_t* current_commit, size_t* peak_commit, size_t* page_faults) mi_attr_noexcept
{ {
mi_subproc_t* subproc = _mi_subproc();
mi_process_info_t pinfo; mi_process_info_t pinfo;
_mi_memzero_var(pinfo); _mi_memzero_var(pinfo);
pinfo.elapsed = _mi_clock_end(mi_process_start); pinfo.elapsed = _mi_clock_end(mi_process_start);
pinfo.current_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&_mi_stats_main.committed.current)); pinfo.current_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)(&subproc->stats.committed.current)));
pinfo.peak_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&_mi_stats_main.committed.peak)); pinfo.peak_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)(&subproc->stats.committed.peak)));
pinfo.current_rss = pinfo.current_commit; pinfo.current_rss = pinfo.current_commit;
pinfo.peak_rss = pinfo.peak_commit; pinfo.peak_rss = pinfo.peak_commit;
pinfo.utime = 0; pinfo.utime = 0;

View file

@ -44,20 +44,18 @@ static int ITER = 10;
static int THREADS = 4; static int THREADS = 4;
static int SCALE = 10; static int SCALE = 10;
static int ITER = 20; static int ITER = 20;
#define ALLOW_LARGE false
#elif 0 #elif 0
static int THREADS = 32; static int THREADS = 32;
static int SCALE = 50; static int SCALE = 50;
static int ITER = 50; static int ITER = 50;
#define ALLOW_LARGE false #elif 1
#elif 0 static int THREADS = 32;
static int THREADS = 64; static int SCALE = 25;
static int SCALE = 400; static int ITER = 50;
static int ITER = 10;
#define ALLOW_LARGE true #define ALLOW_LARGE true
#else #else
static int THREADS = 32; // more repeatable if THREADS <= #processors static int THREADS = 32; // more repeatable if THREADS <= #processors
static int SCALE = 25; // scaling factor static int SCALE = 50; // scaling factor
static int ITER = 50; // N full iterations destructing and re-creating all threads static int ITER = 50; // N full iterations destructing and re-creating all threads
#endif #endif
@ -66,7 +64,7 @@ static int ITER = 50; // N full iterations destructing and re-creating a
#define STRESS // undefine for leak test #define STRESS // undefine for leak test
#ifndef ALLOW_LARGE #ifndef ALLOW_LARGE
#define ALLOW_LARGE true #define ALLOW_LARGE false
#endif #endif
static bool allow_large_objects = ALLOW_LARGE; // allow very large objects? (set to `true` if SCALE>100) static bool allow_large_objects = ALLOW_LARGE; // allow very large objects? (set to `true` if SCALE>100)
@ -363,7 +361,7 @@ int main(int argc, char** argv) {
#else #else
mi_stats_print(NULL); // so we see rss/commit/elapsed mi_stats_print(NULL); // so we see rss/commit/elapsed
#endif #endif
//mi_stats_print(NULL); mi_stats_print(NULL);
//bench_end_program(); //bench_end_program();
return 0; return 0;
} }