diff --git a/ide/vs2022/mimalloc.vcxproj b/ide/vs2022/mimalloc.vcxproj index 63bc7d1d..87e866bb 100644 --- a/ide/vs2022/mimalloc.vcxproj +++ b/ide/vs2022/mimalloc.vcxproj @@ -190,7 +190,7 @@ true Default ../../include - MI_DEBUG=3;MI_GUARDED=0;MI_SECURE=4;%(PreprocessorDefinitions); + MI_DEBUG=3;MI_GUARDED=0;%(PreprocessorDefinitions); CompileAsCpp false stdcpp20 diff --git a/include/mimalloc.h b/include/mimalloc.h index 508e6aec..5f856411 100644 --- a/include/mimalloc.h +++ b/include/mimalloc.h @@ -400,6 +400,7 @@ typedef enum mi_option_e { mi_option_max_page_candidates, // max candidate pages to consider for allocation (=4) mi_option_max_vabits, // max user space virtual address bits to consider (=48) mi_option_pagemap_commit, // commit the full pagemap (to always catch invalid pointer uses) (=0) + mi_option_page_commit_on_demand, // commit page memory on-demand _mi_option_last, // legacy option names mi_option_large_os_pages = mi_option_allow_large_os_pages, diff --git a/include/mimalloc/types.h b/include/mimalloc/types.h index b21d0970..a4e158d6 100644 --- a/include/mimalloc/types.h +++ b/include/mimalloc/types.h @@ -139,6 +139,8 @@ terms of the MIT license. A copy of the license can be found in the file // We never allocate more than PTRDIFF_MAX (see also ) #define MI_MAX_ALLOC_SIZE PTRDIFF_MAX +#define MI_PAGE_MIN_COMMIT_SIZE MI_ARENA_SLICE_SIZE + // ------------------------------------------------------ // Arena's are large reserved areas of memory allocated from // the OS that are managed by mimalloc to efficiently @@ -290,7 +292,7 @@ typedef struct mi_page_s { _Atomic(mi_page_flags_t) xflags; // `in_full_queue` and `has_aligned` flags size_t block_size; // size available in each block (always `>0`) - uint8_t* page_start; // start of the blocks + uint8_t* page_start; // start of the blocks mi_heaptag_t heap_tag; // tag of the owning heap, used to separate heaps by object type bool free_is_zero; // `true` if the blocks in the free list are zero initialized // padding @@ -301,6 +303,7 @@ typedef struct mi_page_s { 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* prev; // previous page owned by the heap with the same `block_size` + size_t page_committed; // committed size relative to `page_start`. mi_memid_t memid; // provenance of the page memory } mi_page_t; @@ -324,7 +327,7 @@ typedef struct mi_page_s { // (Except for large pages since huge objects are allocated in 4MiB chunks) #define MI_SMALL_MAX_OBJ_SIZE ((MI_SMALL_PAGE_SIZE-MI_PAGE_INFO_SIZE)/8) // < 8 KiB #define MI_MEDIUM_MAX_OBJ_SIZE ((MI_MEDIUM_PAGE_SIZE-MI_PAGE_INFO_SIZE)/8) // < 64 KiB -#define MI_LARGE_MAX_OBJ_SIZE ((MI_LARGE_PAGE_SIZE-MI_PAGE_INFO_SIZE)/8) // < 512 KiB +#define MI_LARGE_MAX_OBJ_SIZE ((MI_LARGE_PAGE_SIZE-MI_PAGE_INFO_SIZE)/4) // < 512 KiB #define MI_LARGE_MAX_OBJ_WSIZE (MI_LARGE_MAX_OBJ_SIZE/MI_SIZE_SIZE) diff --git a/src/arena.c b/src/arena.c index af0d1d0a..c31f1fe3 100644 --- a/src/arena.c +++ b/src/arena.c @@ -562,7 +562,7 @@ static mi_page_t* mi_arenas_page_try_find_abandoned(mi_subproc_t* subproc, size_ _mi_page_free_collect(page, false); // update `used` count mi_assert_internal(mi_bitmap_is_clearN(arena->slices_free, slice_index, slice_count)); - mi_assert_internal(mi_bitmap_is_setN(arena->slices_committed, slice_index, slice_count)); + mi_assert_internal(mi_option_is_enabled(mi_option_page_commit_on_demand) || mi_bitmap_is_setN(arena->slices_committed, slice_index, slice_count)); mi_assert_internal(mi_bitmap_is_setN(arena->slices_dirty, slice_index, slice_count)); mi_assert_internal(_mi_is_aligned(page, MI_PAGE_ALIGN)); mi_assert_internal(_mi_ptr_page(page)==page); @@ -578,16 +578,16 @@ static mi_page_t* mi_arenas_page_try_find_abandoned(mi_subproc_t* subproc, size_ // Allocate a fresh page static mi_page_t* mi_arenas_page_alloc_fresh(mi_subproc_t* subproc, size_t slice_count, size_t block_size, size_t block_alignment, - mi_arena_t* req_arena, size_t tseq) + mi_arena_t* req_arena, size_t tseq, bool commit) { const bool allow_large = (MI_SECURE < 2); // 2 = guard page at end of each arena page - const bool commit = true; const bool os_align = (block_alignment > MI_PAGE_MAX_OVERALLOC_ALIGN); const size_t page_alignment = MI_ARENA_SLICE_ALIGN; // try to allocate from free space in arena's mi_memid_t memid = _mi_memid_none(); mi_page_t* page = NULL; + const size_t alloc_size = mi_size_of_slices(slice_count); if (!mi_option_is_enabled(mi_option_disallow_arena_alloc) && // allowed to allocate from arena's? !os_align && // not large alignment slice_count <= MI_ARENA_MAX_OBJ_SLICES) // and not too large @@ -604,10 +604,10 @@ static mi_page_t* mi_arenas_page_alloc_fresh(mi_subproc_t* subproc, size_t slice if (os_align) { // note: slice_count already includes the page mi_assert_internal(slice_count >= mi_slice_count_of_size(block_size) + mi_slice_count_of_size(page_alignment)); - page = (mi_page_t*)mi_arena_os_alloc_aligned(mi_size_of_slices(slice_count), block_alignment, page_alignment /* align offset */, commit, allow_large, req_arena, &memid); + page = (mi_page_t*)mi_arena_os_alloc_aligned(alloc_size, block_alignment, page_alignment /* align offset */, commit, allow_large, req_arena, &memid); } else { - page = (mi_page_t*)mi_arena_os_alloc_aligned(mi_size_of_slices(slice_count), page_alignment, 0 /* align offset */, commit, allow_large, req_arena, &memid); + page = (mi_page_t*)mi_arena_os_alloc_aligned(alloc_size, page_alignment, 0 /* align offset */, commit, allow_large, req_arena, &memid); } } @@ -617,25 +617,25 @@ static mi_page_t* mi_arenas_page_alloc_fresh(mi_subproc_t* subproc, size_t slice // guard page at the end of mimalloc page? #if MI_SECURE < 2 - const size_t page_noguard_size = mi_size_of_slices(slice_count); + const size_t page_noguard_size = alloc_size; #else - mi_assert(mi_size_of_slices(slice_count) > _mi_os_secure_guard_page_size()); - const size_t page_noguard_size = mi_size_of_slices(slice_count) - _mi_os_secure_guard_page_size(); + mi_assert(alloc_size > _mi_os_secure_guard_page_size()); + const size_t page_noguard_size = alloc_size - _mi_os_secure_guard_page_size(); if (memid.initially_committed) { _mi_os_secure_guard_page_set_at((uint8_t*)page + page_noguard_size, memid.is_pinned); } #endif // claimed free slices: initialize the page partly - if (!memid.initially_zero) { + if (!memid.initially_zero && memid.initially_committed) { mi_track_mem_undefined(page, slice_count * MI_ARENA_SLICE_SIZE); _mi_memzero_aligned(page, sizeof(*page)); } - else { + else if (memid.initially_committed) { mi_track_mem_defined(page, slice_count * MI_ARENA_SLICE_SIZE); } #if MI_DEBUG > 1 - if (memid.initially_zero) { + if (memid.initially_zero && memid.initially_committed) { if (!mi_mem_is_zero(page, page_noguard_size)) { _mi_error_message(EFAULT, "internal error: page memory was not zero initialized.\n"); memid.initially_zero = false; @@ -644,6 +644,7 @@ static mi_page_t* mi_arenas_page_alloc_fresh(mi_subproc_t* subproc, size_t slice } #endif mi_assert(MI_PAGE_INFO_SIZE >= mi_page_info_size()); + size_t block_start; #if MI_GUARDED // in a guarded build, we align pages with blocks a multiple of an OS page size, to the OS page size @@ -668,9 +669,24 @@ static mi_page_t* mi_arenas_page_alloc_fresh(mi_subproc_t* subproc, size_t slice } const size_t reserved = (os_align ? 1 : (page_noguard_size - block_start) / block_size); mi_assert_internal(reserved > 0 && reserved <= UINT16_MAX); + + // commit first block? + size_t commit_size = 0; + if (!memid.initially_committed) { + 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_os_commit(page, commit_size, &is_zero); + if (!memid.initially_zero && !is_zero) { + _mi_memzero_aligned(page, commit_size); + } + } + + // initialize page->reserved = (uint16_t)reserved; page->page_start = (uint8_t*)page + block_start; page->block_size = block_size; + page->page_committed = (commit_size == 0 ? 0 : commit_size - block_start); mi_assert(commit_size == 0 || commit_size >= block_start + block_size); page->memid = memid; page->free_is_zero = memid.initially_zero; if (block_size > 0 && _mi_is_power_of_two(block_size)) { @@ -704,7 +720,8 @@ static mi_page_t* mi_arenas_page_regular_alloc(mi_heap_t* heap, size_t slice_cou } // 2. find a free block, potentially allocating a new arena - page = mi_arenas_page_alloc_fresh(tld->subproc, slice_count, block_size, 1, req_arena, tld->thread_seq); + page = mi_arenas_page_alloc_fresh(tld->subproc, slice_count, block_size, 1, req_arena, tld->thread_seq, + !mi_option_is_enabled(mi_option_page_commit_on_demand)); if (page != NULL) { mi_assert_internal(page->memid.memkind != MI_MEM_ARENA || page->memid.mem.arena.slice_count == slice_count); _mi_page_init(heap, page); @@ -726,7 +743,7 @@ static mi_page_t* mi_arenas_page_singleton_alloc(mi_heap_t* heap, size_t block_s const size_t slice_count = mi_slice_count_of_size(_mi_align_up(info_size + block_size, _mi_os_secure_guard_page_size()) + _mi_os_secure_guard_page_size()); #endif - mi_page_t* page = mi_arenas_page_alloc_fresh(tld->subproc, slice_count, block_size, block_alignment, req_arena, tld->thread_seq); + mi_page_t* page = mi_arenas_page_alloc_fresh(tld->subproc, slice_count, block_size, block_alignment, req_arena, tld->thread_seq, true /* commit singletons always */); if (page == NULL) return NULL; mi_assert(page->reserved == 1); @@ -779,7 +796,7 @@ void _mi_arenas_page_free(mi_page_t* page) { mi_arena_t* arena = mi_page_arena(page, &slice_index, &slice_count); mi_assert_internal(mi_bitmap_is_clearN(arena->slices_free, slice_index, slice_count)); - mi_assert_internal(mi_bitmap_is_setN(arena->slices_committed, slice_index, slice_count)); + mi_assert_internal(mi_option_is_enabled(mi_option_page_commit_on_demand) || mi_bitmap_is_setN(arena->slices_committed, slice_index, slice_count)); mi_assert_internal(mi_bitmap_is_clearN(arena->pages_abandoned[bin], slice_index, 1)); mi_assert_internal(mi_bitmap_is_setN(page->memid.mem.arena.arena->pages, page->memid.mem.arena.slice_index, 1)); // note: we cannot check for `!mi_page_is_abandoned_and_mapped` since that may @@ -799,7 +816,16 @@ void _mi_arenas_page_free(mi_page_t* page) { // unregister page _mi_page_map_unregister(page); if (page->memid.memkind == MI_MEM_ARENA) { - mi_bitmap_clear(page->memid.mem.arena.arena->pages, page->memid.mem.arena.slice_index); + mi_arena_t* arena = page->memid.mem.arena.arena; + mi_bitmap_clear(arena->pages, page->memid.mem.arena.slice_index); + if (page->page_committed > 0) { + // if committed on-demand, set the commit bits to account commit properly + const size_t total_committed = (page->page_start - (uint8_t*)page) + page->page_committed; + mi_assert_internal(mi_memid_size(page->memid) >= total_committed); + const size_t total_slices = _mi_divide_up(total_committed, MI_ARENA_SLICE_SIZE); + mi_assert_internal(page->memid.mem.arena.slice_count >= total_slices); + mi_bitmap_setN(arena->slices_committed, page->memid.mem.arena.slice_index, total_slices, NULL); + } } _mi_arenas_free(page, mi_memid_size(page->memid), page->memid); } @@ -824,7 +850,7 @@ void _mi_arenas_page_abandon(mi_page_t* page) { mi_arena_t* arena = mi_page_arena(page, &slice_index, &slice_count); mi_assert_internal(!mi_page_is_singleton(page)); mi_assert_internal(mi_bitmap_is_clearN(arena->slices_free, slice_index, slice_count)); - mi_assert_internal(mi_bitmap_is_setN(arena->slices_committed, slice_index, slice_count)); + mi_assert_internal(mi_option_is_enabled(mi_option_page_commit_on_demand) || mi_bitmap_is_setN(arena->slices_committed, slice_index, slice_count)); mi_assert_internal(mi_bitmap_is_setN(arena->slices_dirty, slice_index, slice_count)); mi_page_set_abandoned_mapped(page); @@ -889,7 +915,7 @@ void _mi_arenas_page_unabandon(mi_page_t* page) { mi_arena_t* arena = mi_page_arena(page, &slice_index, &slice_count); mi_assert_internal(mi_bitmap_is_clearN(arena->slices_free, slice_index, slice_count)); - mi_assert_internal(mi_bitmap_is_setN(arena->slices_committed, slice_index, slice_count)); + mi_assert_internal(mi_option_is_enabled(mi_option_page_commit_on_demand) || mi_bitmap_is_setN(arena->slices_committed, slice_index, slice_count)); // this busy waits until a concurrent reader (from alloc_abandoned) is done mi_bitmap_clear_once_set(arena->pages_abandoned[bin], slice_index); @@ -1430,7 +1456,7 @@ static long mi_arena_purge_delay(void) { // returns if the memory is no longer committed (versus reset which keeps the commit) static bool mi_arena_purge(mi_arena_t* arena, size_t slice_index, size_t slice_count) { mi_assert_internal(!arena->memid.is_pinned); - mi_assert_internal(mi_bitmap_is_clearN(arena->slices_free, slice_index, slice_count)); + mi_assert_internal(mi_bitmap_is_clearN(arena->slices_free, slice_index, slice_count)); // we own it? const size_t size = mi_size_of_slices(slice_count); void* const p = mi_arena_slice_start(arena, slice_index); @@ -1455,7 +1481,7 @@ static void mi_arena_schedule_purge(mi_arena_t* arena, size_t slice_index, size_ const long delay = mi_arena_purge_delay(); if (arena->memid.is_pinned || delay < 0 || _mi_preloading()) return; // is purging allowed at all? - mi_assert_internal(mi_bitmap_is_clearN(arena->slices_free, slice_index, slice_count)); + mi_assert_internal(mi_bitmap_is_clearN(arena->slices_free, slice_index, slice_count)); // we still own it? if (delay == 0) { // purge directly mi_arena_purge(arena, slice_index, slice_count); diff --git a/src/init.c b/src/init.c index 5240611c..16c1dea4 100644 --- a/src/init.c +++ b/src/init.c @@ -35,6 +35,7 @@ const mi_page_t _mi_page_empty = { #endif NULL, // xheap NULL, NULL, // next, prev + MI_ARENA_SLICE_SIZE, // page_committed MI_MEMID_STATIC // memid }; diff --git a/src/options.c b/src/options.c index 63d8a68f..faeb9da4 100644 --- a/src/options.c +++ b/src/options.c @@ -144,7 +144,7 @@ static mi_option_desc_t options[_mi_option_last] = #else { 1, UNINIT, MI_OPTION(eager_commit_delay) }, // the first N segments per thread are not eagerly committed (but per page in the segment on demand) #endif - { 500, UNINIT, MI_OPTION_LEGACY(purge_delay,reset_delay) }, // purge delay in milli-seconds + { 250, UNINIT, MI_OPTION_LEGACY(purge_delay,reset_delay) }, // purge delay in milli-seconds { 0, UNINIT, MI_OPTION(use_numa_nodes) }, // 0 = use available numa nodes, otherwise use at most N nodes. { 0, UNINIT, MI_OPTION_LEGACY(disallow_os_alloc,limit_os_alloc) }, // 1 = do not use OS memory for allocation (but only reserved arenas) { 100, UNINIT, MI_OPTION(os_tag) }, // only apple specific for now but might serve more or less related purpose @@ -175,6 +175,7 @@ static mi_option_desc_t options[_mi_option_last] = { 0, UNINIT, MI_OPTION(max_vabits) }, { MI_DEFAULT_PAGEMAP_COMMIT, UNINIT, MI_OPTION(pagemap_commit) }, // commit the full pagemap upfront? + { 0, UNINIT, MI_OPTION(page_commit_on_demand) }, }; static void mi_option_init(mi_option_desc_t* desc); diff --git a/src/page.c b/src/page.c index 239d5d6e..ed94cae1 100644 --- a/src/page.c +++ b/src/page.c @@ -606,6 +606,18 @@ static void mi_page_extend_free(mi_heap_t* heap, mi_page_t* page) { mi_assert_internal(extend > 0 && extend + page->capacity <= page->reserved); mi_assert_internal(extend < (1UL<<16)); + // commit on demand? + if (page->page_committed > 0) { + const size_t needed_size = (page->capacity + extend)*bsize; + if (needed_size > page->page_committed) { + size_t commit_size = _mi_align_up(needed_size, MI_PAGE_MIN_COMMIT_SIZE); + const size_t max_size = page->reserved * bsize; + if (commit_size > max_size) { commit_size = max_size; } + mi_assert(commit_size > page->page_committed); + _mi_os_commit(mi_page_start(page) + page->page_committed, commit_size - page->page_committed, NULL); + } + } + // and append the extend the free list if (extend < MI_MIN_SLICES || MI_SECURE<3) { //!mi_option_is_enabled(mi_option_secure)) { mi_page_free_list_extend(page, bsize, extend, &heap->tld->stats ); @@ -635,8 +647,8 @@ void _mi_page_init(mi_heap_t* heap, mi_page_t* page) { #endif #if MI_DEBUG>2 if (page->memid.initially_zero) { - mi_track_mem_defined(page->page_start, page_size); - mi_assert_expensive(mi_mem_is_zero(page_start, page_size)); + mi_track_mem_defined(page->page_start, (page->page_committed == 0 ? page_size : page->page_committed)); + mi_assert_expensive(mi_mem_is_zero(page_start, (page->page_committed == 0 ? page_size : page->page_committed))); } #endif