Compare commits

...

16 commits

Author SHA1 Message Date
Daan
a067bd42ae merge from dev (support for mi_prim_reuse, #1097) 2025-06-06 18:05:13 -07:00
Daan
d389819cc9 add initial support for _mi_prim_reuse and MADV_FREE_REUSABLE on macOS (issue #1097) 2025-06-06 17:59:46 -07:00
Daan
aaedb58c09 Merge branch 'dev' into dev3 2025-06-06 15:53:59 -07:00
Daan
2d34956bed use main instead of master in readme 2025-06-06 15:53:28 -07:00
Daan
317b22cdb1 check return value of mi_arena_commit, potential fix for #1098 2025-06-06 15:51:34 -07:00
Daan
c8c325c8cd fix merge error 2025-06-06 15:46:31 -07:00
Daan
5115ddd6e6 fix windows 32-bit compilation 2025-06-06 15:29:59 -07:00
Daan
7512b0ef74 merge from dev 2025-06-06 15:22:16 -07:00
Daan
93a003e2f1 Merge remote-tracking branch 'refs/remotes/origin/dev' into dev 2025-06-06 15:15:47 -07:00
Daan
6c3d75a355 fix base address if commit fails on aligned overallocation 2025-06-06 15:13:43 -07:00
Daan
21425bc334 check all os_commit calls and return NULL on failure 2025-06-06 15:13:43 -07:00
Daan
30a17bf1b7 fix missing csize assignment in _mi_os_free_ex 2025-06-06 15:13:43 -07:00
daanx
08b64e71a7 use subproc_main for final statistics output to avoid dereferencing the heap 2025-06-06 14:38:20 -07:00
daanx
7c7ecf096f ensure use of subproc_main during unsafe destroy to avoid derefercing the heap 2025-06-06 11:49:44 -07:00
daanx
e19c022238 define mi_decl_align separate from mi_decl_cache_align 2025-05-30 09:36:38 -07:00
daanx
715acc0329 mark assert_fail as cold and noreturn; move assert to internal.h (see issue #1091, and python/cpython#134586) 2025-05-30 09:29:35 -07:00
14 changed files with 166 additions and 89 deletions

View file

@ -6,10 +6,8 @@
trigger:
branches:
include:
- master
- dev3
- dev2
- dev
- main
- dev*
tags:
include:
- v*
@ -196,35 +194,6 @@ jobs:
# Other OS versions (just debug mode)
# ----------------------------------------------------------
- job:
displayName: Windows 2019
pool:
vmImage:
windows-2019
strategy:
matrix:
Debug:
BuildType: debug
cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON
MSBuildConfiguration: Debug
Release:
BuildType: release
cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release
MSBuildConfiguration: Release
steps:
- task: CMake@1
inputs:
workingDirectory: $(BuildType)
cmakeArgs: .. $(cmakeExtraArgs)
- task: MSBuild@1
inputs:
solution: $(BuildType)/libmimalloc.sln
configuration: '$(MSBuildConfiguration)'
msbuildArguments: -m
- script: ctest --verbose --timeout 240 -C $(MSBuildConfiguration)
workingDirectory: $(BuildType)
displayName: CTest
- job:
displayName: Ubuntu 24.04
pool:

View file

@ -8,7 +8,6 @@ terms of the MIT license. A copy of the license can be found in the file
#ifndef MI_INTERNAL_H
#define MI_INTERNAL_H
// --------------------------------------------------------------------------
// This file contains the internal API's of mimalloc and various utility
// functions and macros.
@ -18,6 +17,17 @@ terms of the MIT license. A copy of the license can be found in the file
#include "track.h"
#include "bits.h"
// --------------------------------------------------------------------------
// Compiler defines
// --------------------------------------------------------------------------
#if (MI_DEBUG>0)
#define mi_trace_message(...) _mi_trace_message(__VA_ARGS__)
#else
#define mi_trace_message(...)
#endif
#define mi_decl_cache_align mi_decl_align(64)
#if defined(_MSC_VER)
@ -26,26 +36,59 @@ terms of the MIT license. A copy of the license can be found in the file
#define mi_decl_noinline __declspec(noinline)
#define mi_decl_thread __declspec(thread)
#define mi_decl_align(a) __declspec(align(a))
#define mi_decl_noreturn __declspec(noreturn)
#define mi_decl_weak
#define mi_decl_hidden
#define mi_decl_cold
#elif (defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__) // includes clang and icc
#define mi_decl_noinline __attribute__((noinline))
#define mi_decl_thread __thread
#define mi_decl_align(a) __attribute__((aligned(a)))
#define mi_decl_noreturn __attribute__((noreturn))
#define mi_decl_weak __attribute__((weak))
#define mi_decl_hidden __attribute__((visibility("hidden")))
#if (__GNUC__ >= 4) || defined(__clang__)
#define mi_decl_cold __attribute__((cold))
#else
#define mi_decl_cold
#endif
#elif __cplusplus >= 201103L // c++11
#define mi_decl_noinline
#define mi_decl_thread thread_local
#define mi_decl_cache_align alignas(MI_CACHE_LINE)
#define mi_decl_align(a) alignas(a)
#define mi_decl_noreturn [[noreturn]]
#define mi_decl_weak
#define mi_decl_hidden
#define mi_decl_cold
#else
#define mi_decl_noinline
#define mi_decl_thread __thread // hope for the best :-)
#define mi_decl_align(a)
#define mi_decl_noreturn
#define mi_decl_weak
#define mi_decl_hidden
#define mi_decl_cold
#endif
#if defined(__GNUC__) || defined(__clang__)
#define mi_unlikely(x) (__builtin_expect(!!(x),false))
#define mi_likely(x) (__builtin_expect(!!(x),true))
#elif (defined(__cplusplus) && (__cplusplus >= 202002L)) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)
#define mi_unlikely(x) (x) [[unlikely]]
#define mi_likely(x) (x) [[likely]]
#else
#define mi_unlikely(x) (x)
#define mi_likely(x) (x)
#endif
#ifndef __has_builtin
#define __has_builtin(x) 0
#endif
#if defined(__cplusplus)
#define mi_decl_externc extern "C"
#else
#define mi_decl_externc
#endif
#if (defined(__GNUC__) && (__GNUC__ >= 7)) || defined(__clang__) // includes clang and icc
@ -67,11 +110,10 @@ terms of the MIT license. A copy of the license can be found in the file
#define __wasi__
#endif
#if (MI_DEBUG>0)
#define mi_trace_message(...) _mi_trace_message(__VA_ARGS__)
#else
#define mi_trace_message(...)
#endif
// --------------------------------------------------------------------------
// Internal functions
// --------------------------------------------------------------------------
// "libc.c"
@ -135,7 +177,7 @@ void _mi_os_init(void); // c
void* _mi_os_alloc(size_t size, mi_memid_t* memid);
void* _mi_os_zalloc(size_t size, mi_memid_t* memid);
void _mi_os_free(void* p, size_t size, mi_memid_t memid);
void _mi_os_free_ex(void* p, size_t size, bool still_committed, mi_memid_t memid);
void _mi_os_free_ex(void* p, size_t size, bool still_committed, mi_memid_t memid, mi_subproc_t* subproc );
size_t _mi_os_page_size(void);
size_t _mi_os_guard_page_size(void);
@ -146,6 +188,7 @@ size_t _mi_os_virtual_address_bits(void);
bool _mi_os_reset(void* addr, size_t size);
bool _mi_os_decommit(void* addr, size_t size);
void _mi_os_reuse(void* p, size_t size);
mi_decl_nodiscard bool _mi_os_commit(void* p, size_t size, bool* is_zero);
mi_decl_nodiscard bool _mi_os_commit_ex(void* addr, size_t size, bool* is_zero, size_t stat_size);
mi_decl_nodiscard bool _mi_os_protect(void* addr, size_t size);
@ -200,7 +243,7 @@ void _mi_page_map_register(mi_page_t* page);
void _mi_page_map_unregister(mi_page_t* page);
void _mi_page_map_unregister_range(void* start, size_t size);
mi_page_t* _mi_safe_ptr_page(const void* p);
void _mi_page_map_unsafe_destroy(void);
void _mi_page_map_unsafe_destroy(mi_subproc_t* subproc);
// "page.c"
void* _mi_malloc_generic(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment) mi_attr_noexcept mi_attr_malloc;
@ -239,6 +282,7 @@ void _mi_heap_page_reclaim(mi_heap_t* heap, mi_page_t* page);
// "stats.c"
void _mi_stats_init(void);
void _mi_stats_done(mi_stats_t* stats);
void _mi_stats_print(mi_stats_t* stats, mi_output_fun* out, void* arg) mi_attr_noexcept;
void _mi_stats_merge_thread(mi_tld_t* tld);
void _mi_stats_merge_from(mi_stats_t* to, mi_stats_t* from);
mi_msecs_t _mi_clock_now(void);
@ -260,14 +304,13 @@ bool _mi_page_is_valid(mi_page_t* page);
#endif
/* -----------------------------------------------------------
Assertions
----------------------------------------------------------- */
// ------------------------------------------------------
// Assertions
// ------------------------------------------------------
#if (MI_DEBUG)
// use our own assertion to print without memory allocation
void _mi_assert_fail(const char* assertion, const char* fname, unsigned int line, const char* func);
mi_decl_noreturn mi_decl_cold void _mi_assert_fail(const char* assertion, const char* fname, unsigned int line, const char* func) mi_attr_noexcept;
#define mi_assert(expr) ((expr) ? (void)0 : _mi_assert_fail(#expr,__FILE__,__LINE__,__func__))
#else
#define mi_assert(x)
@ -285,6 +328,7 @@ void _mi_assert_fail(const char* assertion, const char* fname, unsigned int line
#define mi_assert_expensive(x)
#endif
/* -----------------------------------------------------------
Statistics (in `stats.c`)
----------------------------------------------------------- */
@ -343,6 +387,8 @@ typedef struct mi_option_desc_s {
const char* legacy_name; // potential legacy option name
} mi_option_desc_t;
/* -----------------------------------------------------------
Inlined definitions
----------------------------------------------------------- */

View file

@ -63,6 +63,11 @@ int _mi_prim_decommit(void* addr, size_t size, bool* needs_recommit);
// Returns error code or 0 on success.
int _mi_prim_reset(void* addr, size_t size);
// Reuse memory. This is called for memory that is already committed but
// may have been reset (`_mi_prim_reset`) or decommitted (`_mi_prim_decommit`) where `needs_recommit` was false.
// Returns error code or 0 on success. On most platforms this is a no-op.
int _mi_prim_reuse(void* addr, size_t size);
// Protect memory. Returns error code or 0 on success.
int _mi_prim_protect(void* addr, size_t size, bool protect);

View file

@ -72,15 +72,14 @@ Enjoy!
### Branches
* `master`: latest stable release (still based on `dev2`).
* `main`: latest stable release (still based on `dev2`).
* `dev`: development branch for mimalloc v1. Use this branch for submitting PR's.
* `dev2`: development branch for mimalloc v2. This branch is downstream of `dev`
(and is essentially equal to `dev` except for `src/segment.c`). Uses larger sliced segments to manage
mimalloc pages that can reduce fragmentation.
* `dev3`: development branch for mimalloc v3-beta. This branch is downstream of `dev`. This version
simplifies the lock-free ownership of previous versions, has no thread-local segments any more.
This improves sharing of memory between threads, and on certain large workloads may use less memory
with less fragmentation.
This improves sharing of memory between threads, and on certain large workloads may use (much) less memory.
### Releases

View file

@ -76,7 +76,7 @@ size_t mi_arena_min_alignment(void) {
return MI_ARENA_SLICE_ALIGN;
}
static bool mi_arena_commit(mi_arena_t* arena, void* start, size_t size, bool* is_zero, size_t already_committed) {
mi_decl_nodiscard static bool mi_arena_commit(mi_arena_t* arena, void* start, size_t size, bool* is_zero, size_t already_committed) {
if (arena != NULL && arena->commit_fun != NULL) {
return (*arena->commit_fun)(true, start, size, is_zero, arena->commit_fun_arg);
}
@ -216,12 +216,13 @@ static mi_decl_noinline void* mi_arena_try_alloc_at(
}
}
else {
// already fully commited.
// already fully committed.
_mi_os_reuse(p, mi_size_of_slices(slice_count));
// if the OS has overcommit, and this is the first time we access these pages, then
// count the commit now (as at arena reserve we didn't count those commits as these are on-demand)
if (_mi_os_has_overcommit() && touched_slices > 0) {
mi_subproc_stat_increase( arena->subproc, committed, mi_size_of_slices(touched_slices));
}
}
}
// tool support
if (memid->initially_zero) {
@ -677,7 +678,10 @@ static mi_page_t* mi_arenas_page_alloc_fresh(size_t slice_count, size_t block_si
commit_size = _mi_align_up(block_start + block_size, MI_PAGE_MIN_COMMIT_SIZE);
if (commit_size > page_noguard_size) { commit_size = page_noguard_size; }
bool is_zero;
mi_arena_commit( mi_memid_arena(memid), page, commit_size, &is_zero, 0);
if (!mi_arena_commit( mi_memid_arena(memid), page, commit_size, &is_zero, 0)) {
_mi_arenas_free(page, alloc_size, memid);
return NULL;
}
if (!memid.initially_zero && !is_zero) {
_mi_memzero_aligned(page, commit_size);
}
@ -1074,6 +1078,7 @@ bool _mi_arenas_contain(const void* p) {
// destroy owned arenas; this is unsafe and should only be done using `mi_option_destroy_on_exit`
// for dynamic libraries that are unloaded and need to release all their allocated memory.
static void mi_arenas_unsafe_destroy(mi_subproc_t* subproc) {
mi_assert_internal(subproc != NULL);
const size_t max_arena = mi_arenas_get_count(subproc);
size_t new_max_arena = 0;
for (size_t i = 0; i < max_arena; i++) {
@ -1082,7 +1087,7 @@ static void mi_arenas_unsafe_destroy(mi_subproc_t* subproc) {
// mi_lock_done(&arena->abandoned_visit_lock);
mi_atomic_store_ptr_release(mi_arena_t, &subproc->arenas[i], NULL);
if (mi_memkind_is_os(arena->memid.memkind)) {
_mi_os_free(mi_arena_start(arena), mi_arena_size(arena), arena->memid);
_mi_os_free_ex(mi_arena_start(arena), mi_arena_size(arena), true, arena->memid, subproc); // pass `subproc` to avoid accessing the heap pointer (in `_mi_subproc()`)
}
}
}
@ -1096,7 +1101,7 @@ static void mi_arenas_unsafe_destroy(mi_subproc_t* subproc) {
// destroy owned arenas; this is unsafe and should only be done using `mi_option_destroy_on_exit`
// for dynamic libraries that are unloaded and need to release all their allocated memory.
void _mi_arenas_unsafe_destroy_all(mi_tld_t* tld) {
mi_arenas_unsafe_destroy(_mi_subproc());
mi_arenas_unsafe_destroy(tld->subproc);
_mi_arenas_collect(true /* force purge */, true /* visit all*/, tld); // purge non-owned arenas
}
@ -1304,7 +1309,7 @@ static int mi_reserve_os_memory_ex2(mi_subproc_t* subproc, size_t size, bool com
void* start = _mi_os_alloc_aligned(size, MI_ARENA_SLICE_ALIGN, commit, allow_large, &memid);
if (start == NULL) return ENOMEM;
if (!mi_manage_os_memory_ex2(subproc, start, size, -1 /* numa node */, exclusive, memid, NULL, NULL, arena_id)) {
_mi_os_free_ex(start, size, commit, memid);
_mi_os_free_ex(start, size, commit, memid, NULL);
_mi_verbose_message("failed to reserve %zu KiB memory\n", _mi_divide_up(size, 1024));
return ENOMEM;
}

View file

@ -779,11 +779,12 @@ void mi_cdecl _mi_process_done(void) {
mi_heap_collect(heap, true /* force */);
_mi_heap_unsafe_destroy_all(heap); // forcefully release all memory held by all heaps (of this thread only!)
_mi_arenas_unsafe_destroy_all(heap->tld);
_mi_page_map_unsafe_destroy();
_mi_page_map_unsafe_destroy(_mi_subproc_main());
}
//_mi_page_map_unsafe_destroy(_mi_subproc_main());
if (mi_option_is_enabled(mi_option_show_stats) || mi_option_is_enabled(mi_option_verbose)) {
mi_stats_print(NULL);
_mi_stats_print(&_mi_subproc_main()->stats, NULL, NULL); // use always main subproc at process exit to avoid dereferencing the heap (as it may be destroyed by now)
}
_mi_allocator_done();
_mi_verbose_message("process done: 0x%zx\n", tld_main.thread_id);

View file

@ -405,8 +405,10 @@ void mi_register_output(mi_output_fun* out, void* arg) mi_attr_noexcept {
// add stderr to the delayed output after the module is loaded
static void mi_add_stderr_output(void) {
mi_assert_internal(mi_out_default == NULL);
mi_out_buf_flush(&mi_out_stderr, false, NULL); // flush current contents to stderr
mi_out_default = &mi_out_buf_stderr; // and add stderr to the delayed output
if (mi_out_default==NULL) {
mi_out_buf_flush(&mi_out_stderr, false, NULL); // flush current contents to stderr
mi_out_default = &mi_out_buf_stderr; // and add stderr to the delayed output
}
}
// --------------------------------------------------------
@ -544,7 +546,7 @@ void _mi_warning_message(const char* fmt, ...) {
#if MI_DEBUG
void _mi_assert_fail(const char* assertion, const char* fname, unsigned line, const char* func ) {
mi_decl_noreturn mi_decl_cold void _mi_assert_fail(const char* assertion, const char* fname, unsigned line, const char* func ) mi_attr_noexcept {
_mi_fprintf(NULL, NULL, "mimalloc: assertion failed: at \"%s\":%u, %s\n assertion: \"%s\"\n", fname, line, (func==NULL?"":func), assertion);
abort();
}

View file

@ -168,22 +168,23 @@ bool _mi_os_secure_guard_page_reset_before(void* addr, mi_memid_t memid) {
Free memory
-------------------------------------------------------------- */
static void mi_os_free_huge_os_pages(void* p, size_t size);
static void mi_os_free_huge_os_pages(void* p, size_t size, mi_subproc_t* subproc);
static void mi_os_prim_free(void* addr, size_t size, size_t commit_size) {
static void mi_os_prim_free(void* addr, size_t size, size_t commit_size, mi_subproc_t* subproc) {
mi_assert_internal((size % _mi_os_page_size()) == 0);
if (addr == NULL) return; // || _mi_os_is_huge_reserved(addr)
int err = _mi_prim_free(addr, size); // allow size==0 (issue #1041)
if (err != 0) {
_mi_warning_message("unable to free OS memory (error: %d (0x%x), size: 0x%zx bytes, address: %p)\n", err, err, size, addr);
}
if (subproc == NULL) { subproc = _mi_subproc(); } // from `mi_arenas_unsafe_destroy` we pass subproc_main explicitly as we can no longer use the heap pointer
if (commit_size > 0) {
mi_os_stat_decrease(committed, commit_size);
mi_subproc_stat_decrease(subproc, committed, commit_size);
}
mi_os_stat_decrease(reserved, size);
mi_subproc_stat_decrease(subproc, 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, mi_subproc_t* subproc /* can be NULL */) {
if (mi_memkind_is_os(memid.memkind)) {
size_t csize = memid.mem.os.size;
if (csize==0) { csize = _mi_os_good_alloc_size(size); }
@ -204,10 +205,10 @@ void _mi_os_free_ex(void* addr, size_t size, bool still_committed, mi_memid_t me
// free it
if (memid.memkind == MI_MEM_OS_HUGE) {
mi_assert(memid.is_pinned);
mi_os_free_huge_os_pages(base, csize);
mi_os_free_huge_os_pages(base, csize, subproc);
}
else {
mi_os_prim_free(base, csize, (still_committed ? commit_size : 0));
mi_os_prim_free(base, csize, (still_committed ? commit_size : 0), subproc);
}
}
else {
@ -217,7 +218,7 @@ void _mi_os_free_ex(void* addr, size_t size, bool still_committed, mi_memid_t me
}
void _mi_os_free(void* p, size_t size, mi_memid_t memid) {
_mi_os_free_ex(p, size, true, memid);
_mi_os_free_ex(p, size, true, memid, NULL);
}
@ -292,7 +293,7 @@ static void* mi_os_prim_alloc_aligned(size_t size, size_t alignment, bool commit
_mi_warning_message("unable to allocate aligned OS memory directly, fall back to over-allocation (size: 0x%zx bytes, address: %p, alignment: 0x%zx, commit: %d)\n", size, p, alignment, commit);
}
#endif
if (p != NULL) { mi_os_prim_free(p, size, (commit ? size : 0)); }
if (p != NULL) { mi_os_prim_free(p, size, (commit ? size : 0), NULL); }
if (size >= (SIZE_MAX - alignment)) return NULL; // overflow
const size_t over_size = size + alignment;
@ -310,7 +311,7 @@ static void* mi_os_prim_alloc_aligned(size_t size, size_t alignment, bool commit
// explicitly commit only the aligned part
if (commit) {
if (!_mi_os_commit(p, size, NULL)) {
mi_os_prim_free(*base, over_size, 0);
mi_os_prim_free(*base, over_size, 0, NULL);
return NULL;
}
}
@ -326,8 +327,8 @@ static void* mi_os_prim_alloc_aligned(size_t size, size_t alignment, bool commit
size_t mid_size = _mi_align_up(size, _mi_os_page_size());
size_t post_size = over_size - pre_size - mid_size;
mi_assert_internal(pre_size < over_size&& post_size < over_size&& mid_size >= size);
if (pre_size > 0) { mi_os_prim_free(p, pre_size, (commit ? pre_size : 0)); }
if (post_size > 0) { mi_os_prim_free((uint8_t*)aligned_p + mid_size, post_size, (commit ? post_size : 0)); }
if (pre_size > 0) { mi_os_prim_free(p, pre_size, (commit ? pre_size : 0), NULL); }
if (post_size > 0) { mi_os_prim_free((uint8_t*)aligned_p + mid_size, post_size, (commit ? post_size : 0), NULL); }
// we can return the aligned pointer on `mmap` systems
p = aligned_p;
*base = aligned_p; // since we freed the pre part, `*base == p`.
@ -531,6 +532,17 @@ bool _mi_os_reset(void* addr, size_t size) {
}
void _mi_os_reuse( void* addr, size_t size ) {
// page align conservatively within the range
size_t csize = 0;
void* const start = mi_os_page_align_area_conservative(addr, size, &csize);
if (csize == 0) return;
const int err = _mi_prim_reuse(start, csize);
if (err != 0) {
_mi_warning_message("cannot reuse OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes)\n", err, err, start, csize);
}
}
// either resets or decommits memory, returns true if the memory needs
// to be recommitted if it is to be re-used later on.
bool _mi_os_purge_ex(void* p, size_t size, bool allow_reset, size_t stat_size, mi_commit_fun_t* commit_fun, void* commit_fun_arg)
@ -668,7 +680,7 @@ void* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_mse
// no success, issue a warning and break
if (p != NULL) {
_mi_warning_message("could not allocate contiguous huge OS page %zu at %p\n", page, addr);
mi_os_prim_free(p, MI_HUGE_OS_PAGE_SIZE, MI_HUGE_OS_PAGE_SIZE);
mi_os_prim_free(p, MI_HUGE_OS_PAGE_SIZE, MI_HUGE_OS_PAGE_SIZE, NULL);
}
break;
}
@ -710,11 +722,11 @@ void* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_mse
// free every huge page in a range individually (as we allocated per page)
// note: needed with VirtualAlloc but could potentially be done in one go on mmap'd systems.
static void mi_os_free_huge_os_pages(void* p, size_t size) {
static void mi_os_free_huge_os_pages(void* p, size_t size, mi_subproc_t* subproc) {
if (p==NULL || size==0) return;
uint8_t* base = (uint8_t*)p;
while (size >= MI_HUGE_OS_PAGE_SIZE) {
mi_os_prim_free(base, MI_HUGE_OS_PAGE_SIZE, MI_HUGE_OS_PAGE_SIZE);
mi_os_prim_free(base, MI_HUGE_OS_PAGE_SIZE, MI_HUGE_OS_PAGE_SIZE, subproc);
size -= MI_HUGE_OS_PAGE_SIZE;
base += MI_HUGE_OS_PAGE_SIZE;
}

View file

@ -78,10 +78,11 @@ bool _mi_page_map_init(void) {
return true;
}
void _mi_page_map_unsafe_destroy(void) {
void _mi_page_map_unsafe_destroy(mi_subproc_t* subproc) {
mi_assert_internal(subproc != NULL);
mi_assert_internal(_mi_page_map != NULL);
if (_mi_page_map == NULL) return;
_mi_os_free(mi_page_map_memid.mem.os.base, mi_page_map_memid.mem.os.size, mi_page_map_memid);
_mi_os_free_ex(mi_page_map_memid.mem.os.base, mi_page_map_memid.mem.os.size, true, mi_page_map_memid, subproc);
_mi_page_map = NULL;
mi_page_map_commit = NULL;
mi_page_map_max_address = NULL;
@ -265,7 +266,8 @@ bool _mi_page_map_init(void) {
}
void _mi_page_map_unsafe_destroy(void) {
void _mi_page_map_unsafe_destroy(mi_subproc_t* subproc) {
mi_assert_internal(subproc != NULL);
mi_assert_internal(_mi_page_map != NULL);
if (_mi_page_map == NULL) return;
for (size_t idx = 1; idx < mi_page_map_count; idx++) { // skip entry 0 (as we allocate that submap at the end of the page_map)
@ -274,12 +276,12 @@ void _mi_page_map_unsafe_destroy(void) {
mi_page_t** sub = _mi_page_map_at(idx);
if (sub != NULL) {
mi_memid_t memid = _mi_memid_create_os(sub, MI_PAGE_MAP_SUB_SIZE, true, false, false);
_mi_os_free(memid.mem.os.base, memid.mem.os.size, memid);
_mi_os_free_ex(memid.mem.os.base, memid.mem.os.size, true, memid, subproc);
mi_atomic_store_ptr_release(mi_page_t*, &_mi_page_map[idx], NULL);
}
}
}
_mi_os_free(_mi_page_map, mi_page_map_memid.mem.os.size, mi_page_map_memid);
_mi_os_free_ex(_mi_page_map, mi_page_map_memid.mem.os.size, true, mi_page_map_memid, subproc);
_mi_page_map = NULL;
mi_page_map_count = 0;
mi_page_map_memid = _mi_memid_none();

View file

@ -114,6 +114,11 @@ int _mi_prim_reset(void* addr, size_t size) {
return 0;
}
int _mi_prim_reuse(void* addr, size_t size) {
MI_UNUSED(addr); MI_UNUSED(size);
return 0;
}
int _mi_prim_protect(void* addr, size_t size, bool protect) {
MI_UNUSED(addr); MI_UNUSED(size); MI_UNUSED(protect);
return 0;

View file

@ -434,13 +434,27 @@ int _mi_prim_commit(void* start, size_t size, bool* is_zero) {
return err;
}
int _mi_prim_reuse(void* start, size_t size) {
#if defined(__APPLE__) && defined(MADV_FREE_REUSE)
return unix_madvise(start, size, MADV_FREE_REUSE);
#endif
return 0;
}
int _mi_prim_decommit(void* start, size_t size, bool* needs_recommit) {
int err = 0;
// decommit: use MADV_DONTNEED as it decreases rss immediately (unlike MADV_FREE)
err = unix_madvise(start, size, MADV_DONTNEED);
#if !MI_DEBUG && MI_SECURE<=2
*needs_recommit = false;
#if defined(__APPLE__) && defined(MADV_FREE_REUSABLE)
// decommit on macOS: use MADV_FREE_REUSABLE as it does immediate rss accounting (issue #1097)
err = unix_madvise(start, size, MADV_FREE_REUSABLE);
#else
// decommit: use MADV_DONTNEED as it decreases rss immediately (unlike MADV_FREE)
err = unix_madvise(start, size, MADV_DONTNEED);
#endif
#else
// note: don't use MADV_FREE_REUSABLE as the range may contain protected areas
err = unix_madvise(start, size, MADV_DONTNEED);
*needs_recommit = true;
mprotect(start, size, PROT_NONE);
#endif
@ -455,14 +469,21 @@ int _mi_prim_decommit(void* start, size_t size, bool* needs_recommit) {
}
int _mi_prim_reset(void* start, size_t size) {
// We try to use `MADV_FREE` as that is the fastest. A drawback though is that it
int err = 0;
#if defined(__APPLE__) && defined(MADV_FREE_REUSABLE)
// on macOS we try to use MADV_FREE_REUSABLE as it seems the fastest
err = unix_madvise(start, size, MADV_FREE_REUSABLE);
if (err == 0) return 0;
// fall through
#endif
#if defined(MADV_FREE)
// Otherwise, we try to use `MADV_FREE` as that is the fastest. A drawback though is that it
// will not reduce the `rss` stats in tools like `top` even though the memory is available
// to other processes. With the default `MIMALLOC_PURGE_DECOMMITS=1` we ensure that by
// default `MADV_DONTNEED` is used though.
#if defined(MADV_FREE)
static _Atomic(size_t) advice = MI_ATOMIC_VAR_INIT(MADV_FREE);
int oadvice = (int)mi_atomic_load_relaxed(&advice);
int err;
while ((err = unix_madvise(start, size, oadvice)) != 0 && errno == EAGAIN) { errno = 0; };
if (err != 0 && errno == EINVAL && oadvice == MADV_FREE) {
// if MADV_FREE is not supported, fall back to MADV_DONTNEED from now on
@ -470,7 +491,7 @@ int _mi_prim_reset(void* start, size_t size) {
err = unix_madvise(start, size, MADV_DONTNEED);
}
#else
int err = unix_madvise(start, size, MADV_DONTNEED);
err = unix_madvise(start, size, MADV_DONTNEED);
#endif
return err;
}

View file

@ -149,6 +149,11 @@ int _mi_prim_reset(void* addr, size_t size) {
return 0;
}
int _mi_prim_reuse(void* addr, size_t size) {
MI_UNUSED(addr); MI_UNUSED(size);
return 0;
}
int _mi_prim_protect(void* addr, size_t size, bool protect) {
MI_UNUSED(addr); MI_UNUSED(size); MI_UNUSED(protect);
return 0;

View file

@ -376,6 +376,11 @@ int _mi_prim_reset(void* addr, size_t size) {
return (p != NULL ? 0 : (int)GetLastError());
}
int _mi_prim_reuse(void* addr, size_t size) {
MI_UNUSED(addr); MI_UNUSED(size);
return 0;
}
int _mi_prim_protect(void* addr, size_t size, bool protect) {
DWORD oldprotect = 0;
BOOL ok = VirtualProtect(addr, size, protect ? PAGE_NOACCESS : PAGE_READWRITE, &oldprotect);

View file

@ -310,7 +310,7 @@ static void mi_cdecl mi_buffered_out(const char* msg, void* arg) {
// Print statistics
//------------------------------------------------------------
static void _mi_stats_print(mi_stats_t* stats, mi_output_fun* out0, void* arg0) mi_attr_noexcept {
void _mi_stats_print(mi_stats_t* stats, mi_output_fun* out0, void* arg0) mi_attr_noexcept {
// wrap the output function to be line buffered
char buf[256]; _mi_memzero_var(buf);
buffered_t buffer = { out0, arg0, NULL, 0, 255 };