Skip to content

Commit 9b75377

Browse files
committed
Merge branch 'dev-steal' into dev
2 parents cd61eb7 + 7673aa2 commit 9b75377

File tree

8 files changed

+266
-32
lines changed

8 files changed

+266
-32
lines changed

ide/vs2022/mimalloc.vcxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@
116116
<SDLCheck>true</SDLCheck>
117117
<ConformanceMode>Default</ConformanceMode>
118118
<AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>
119-
<PreprocessorDefinitions>MI_DEBUG=4;MI_GUARDED=1;%(PreprocessorDefinitions);</PreprocessorDefinitions>
119+
<PreprocessorDefinitions>MI_DEBUG=3;MI_GUARDED=0;%(PreprocessorDefinitions);</PreprocessorDefinitions>
120120
<CompileAs>CompileAsCpp</CompileAs>
121121
<SupportJustMyCode>false</SupportJustMyCode>
122122
<LanguageStandard>stdcpp20</LanguageStandard>

include/mimalloc.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ typedef void (mi_cdecl mi_error_fun)(int err, void* arg);
148148
mi_decl_export void mi_register_error(mi_error_fun* fun, void* arg);
149149

150150
mi_decl_export void mi_collect(bool force) mi_attr_noexcept;
151+
mi_decl_export void mi_collect_reduce(size_t target_thread_owned) mi_attr_noexcept;
151152
mi_decl_export int mi_version(void) mi_attr_noexcept;
152153
mi_decl_export void mi_stats_reset(void) mi_attr_noexcept;
153154
mi_decl_export void mi_stats_merge(void) mi_attr_noexcept;
@@ -377,6 +378,7 @@ typedef enum mi_option_e {
377378
mi_option_guarded_precise, // disregard minimal alignment requirement to always place guarded blocks exactly in front of a guard page (=0)
378379
mi_option_guarded_sample_rate, // 1 out of N allocations in the min/max range will be guarded (=1000)
379380
mi_option_guarded_sample_seed, // can be set to allow for a (more) deterministic re-execution when a guard page is triggered (=0)
381+
mi_option_target_segments_per_thread, // experimental (=0)
380382
_mi_option_last,
381383
// legacy option names
382384
mi_option_large_os_pages = mi_option_allow_large_os_pages,

include/mimalloc/internal.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ void _mi_page_retire(mi_page_t* page) mi_attr_noexcept; /
178178
void _mi_page_unfull(mi_page_t* page);
179179
void _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force); // free the page
180180
void _mi_page_abandon(mi_page_t* page, mi_page_queue_t* pq); // abandon the page, to be picked up by another thread...
181+
void _mi_page_force_abandon(mi_page_t* page);
182+
181183
void _mi_heap_delayed_free_all(mi_heap_t* heap);
182184
bool _mi_heap_delayed_free_partial(mi_heap_t* heap);
183185
void _mi_heap_collect_retired(mi_heap_t* heap, bool force);
@@ -625,9 +627,9 @@ static inline bool mi_heap_malloc_use_guarded(mi_heap_t* heap, size_t size) {
625627
}
626628
else {
627629
// failed size criteria, rewind count (but don't write to an empty heap)
628-
if (heap->guarded_sample_rate != 0) { heap->guarded_sample_count = 1; }
630+
if (heap->guarded_sample_rate != 0) { heap->guarded_sample_count = 1; }
629631
return false;
630-
}
632+
}
631633
}
632634

633635
mi_decl_restrict void* _mi_heap_malloc_guarded(mi_heap_t* heap, size_t size, bool zero) mi_attr_noexcept;

include/mimalloc/types.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,8 @@ typedef struct mi_segment_s {
416416
// segment fields
417417
struct mi_segment_s* next; // must be the first (non-constant) segment field -- see `segment.c:segment_init`
418418
struct mi_segment_s* prev;
419-
bool was_reclaimed; // true if it was reclaimed (used to limit on-free reclamation)
419+
bool was_reclaimed; // true if it was reclaimed (used to limit reclaim-on-free reclamation)
420+
bool dont_free; // can be temporarily true to ensure the segment is not freed
420421

421422
size_t abandoned; // abandoned pages (i.e. the original owning thread stopped) (`abandoned <= used`)
422423
size_t abandoned_visits; // count how often this segment is visited for reclaiming (to force reclaim if it is too long)

src/options.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ typedef struct mi_option_desc_s {
6565
#define MI_DEFAULT_ARENA_EAGER_COMMIT 2
6666
#endif
6767

68+
// in KiB
6869
#ifndef MI_DEFAULT_ARENA_RESERVE
6970
#if (MI_INTPTR_SIZE>4)
7071
#define MI_DEFAULT_ARENA_RESERVE 1024L*1024L
@@ -156,6 +157,7 @@ static mi_option_desc_t options[_mi_option_last] =
156157
{ MI_DEFAULT_GUARDED_SAMPLE_RATE,
157158
UNINIT, MI_OPTION(guarded_sample_rate)}, // 1 out of N allocations in the min/max range will be guarded (=4000)
158159
{ 0, UNINIT, MI_OPTION(guarded_sample_seed)},
160+
{ 0, UNINIT, MI_OPTION(target_segments_per_thread) }, // abandon segments beyond this point, or 0 to disable.
159161
};
160162

161163
static void mi_option_init(mi_option_desc_t* desc);

src/page-queue.c

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,16 @@ static void mi_page_queue_push(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_
259259
heap->page_count++;
260260
}
261261

262+
static void mi_page_queue_move_to_front(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_t* page) {
263+
mi_assert_internal(mi_page_heap(page) == heap);
264+
mi_assert_internal(mi_page_queue_contains(queue, page));
265+
if (queue->first == page) return;
266+
mi_page_queue_remove(queue, page);
267+
mi_page_queue_push(heap, queue, page);
268+
mi_assert_internal(queue->first == page);
269+
}
262270

263-
static void mi_page_queue_enqueue_from(mi_page_queue_t* to, mi_page_queue_t* from, mi_page_t* page) {
271+
static void mi_page_queue_enqueue_from_ex(mi_page_queue_t* to, mi_page_queue_t* from, bool enqueue_at_end, mi_page_t* page) {
264272
mi_assert_internal(page != NULL);
265273
mi_assert_expensive(mi_page_queue_contains(from, page));
266274
mi_assert_expensive(!mi_page_queue_contains(to, page));
@@ -273,6 +281,8 @@ static void mi_page_queue_enqueue_from(mi_page_queue_t* to, mi_page_queue_t* fro
273281
(mi_page_is_huge(page) && mi_page_queue_is_full(to)));
274282

275283
mi_heap_t* heap = mi_page_heap(page);
284+
285+
// delete from `from`
276286
if (page->prev != NULL) page->prev->next = page->next;
277287
if (page->next != NULL) page->next->prev = page->prev;
278288
if (page == from->last) from->last = page->prev;
@@ -283,22 +293,59 @@ static void mi_page_queue_enqueue_from(mi_page_queue_t* to, mi_page_queue_t* fro
283293
mi_heap_queue_first_update(heap, from);
284294
}
285295

286-
page->prev = to->last;
287-
page->next = NULL;
288-
if (to->last != NULL) {
289-
mi_assert_internal(heap == mi_page_heap(to->last));
290-
to->last->next = page;
291-
to->last = page;
296+
// insert into `to`
297+
if (enqueue_at_end) {
298+
// enqueue at the end
299+
page->prev = to->last;
300+
page->next = NULL;
301+
if (to->last != NULL) {
302+
mi_assert_internal(heap == mi_page_heap(to->last));
303+
to->last->next = page;
304+
to->last = page;
305+
}
306+
else {
307+
to->first = page;
308+
to->last = page;
309+
mi_heap_queue_first_update(heap, to);
310+
}
292311
}
293312
else {
294-
to->first = page;
295-
to->last = page;
296-
mi_heap_queue_first_update(heap, to);
313+
if (to->first != NULL) {
314+
// enqueue at 2nd place
315+
mi_assert_internal(heap == mi_page_heap(to->first));
316+
mi_page_t* next = to->first->next;
317+
page->prev = to->first;
318+
page->next = next;
319+
to->first->next = page;
320+
if (next != NULL) {
321+
next->prev = page;
322+
}
323+
else {
324+
to->last = page;
325+
}
326+
}
327+
else {
328+
// enqueue at the head (singleton list)
329+
page->prev = NULL;
330+
page->next = NULL;
331+
to->first = page;
332+
to->last = page;
333+
mi_heap_queue_first_update(heap, to);
334+
}
297335
}
298336

299337
mi_page_set_in_full(page, mi_page_queue_is_full(to));
300338
}
301339

340+
static void mi_page_queue_enqueue_from(mi_page_queue_t* to, mi_page_queue_t* from, mi_page_t* page) {
341+
mi_page_queue_enqueue_from_ex(to, from, true /* enqueue at the end */, page);
342+
}
343+
344+
static void mi_page_queue_enqueue_from_full(mi_page_queue_t* to, mi_page_queue_t* from, mi_page_t* page) {
345+
// note: we could insert at the front to increase reuse, but it slows down certain benchmarks (like `alloc-test`)
346+
mi_page_queue_enqueue_from_ex(to, from, false /* enqueue at the end of the `to` queue? */, page);
347+
}
348+
302349
// Only called from `mi_heap_absorb`.
303350
size_t _mi_page_queue_append(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_queue_t* append) {
304351
mi_assert_internal(mi_heap_contains_queue(heap,pq));

src/page.c

Lines changed: 92 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ void _mi_page_unfull(mi_page_t* page) {
357357
mi_page_set_in_full(page, false); // to get the right queue
358358
mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page);
359359
mi_page_set_in_full(page, true);
360-
mi_page_queue_enqueue_from(pq, pqfull, page);
360+
mi_page_queue_enqueue_from_full(pq, pqfull, page);
361361
}
362362

363363
static void mi_page_to_full(mi_page_t* page, mi_page_queue_t* pq) {
@@ -403,6 +403,27 @@ void _mi_page_abandon(mi_page_t* page, mi_page_queue_t* pq) {
403403
_mi_segment_page_abandon(page,segments_tld);
404404
}
405405

406+
// force abandon a page
407+
void _mi_page_force_abandon(mi_page_t* page) {
408+
mi_heap_t* heap = mi_page_heap(page);
409+
// mark page as not using delayed free
410+
_mi_page_use_delayed_free(page, MI_NEVER_DELAYED_FREE, false);
411+
412+
// ensure this page is no longer in the heap delayed free list
413+
_mi_heap_delayed_free_all(heap);
414+
// We can still access the page meta-info even if it is freed as we ensure
415+
// in `mi_segment_force_abandon` that the segment is not freed (yet)
416+
if (page->capacity == 0) return; // it may have been freed now
417+
418+
// and now unlink it from the page queue and abandon (or free)
419+
mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page);
420+
if (mi_page_all_free(page)) {
421+
_mi_page_free(page, pq, false);
422+
}
423+
else {
424+
_mi_page_abandon(page, pq);
425+
}
426+
}
406427

407428
// Free a page with no more free blocks
408429
void _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force) {
@@ -448,6 +469,7 @@ void _mi_page_retire(mi_page_t* page) mi_attr_noexcept {
448469
// how to check this efficiently though...
449470
// for now, we don't retire if it is the only page left of this size class.
450471
mi_page_queue_t* pq = mi_page_queue_of(page);
472+
#if MI_RETIRE_CYCLES > 0
451473
const size_t bsize = mi_page_block_size(page);
452474
if mi_likely( /* bsize < MI_MAX_RETIRE_SIZE && */ !mi_page_queue_is_special(pq)) { // not full or huge queue?
453475
if (pq->last==page && pq->first==page) { // the only page in the queue?
@@ -463,7 +485,7 @@ void _mi_page_retire(mi_page_t* page) mi_attr_noexcept {
463485
return; // don't free after all
464486
}
465487
}
466-
488+
#endif
467489
_mi_page_free(page, pq, false);
468490
}
469491

@@ -709,46 +731,94 @@ static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi
709731
Find pages with free blocks
710732
-------------------------------------------------------------*/
711733

734+
// search for a best next page to use for at most N pages (often cut short if immediate blocks are available)
735+
#define MI_MAX_CANDIDATE_SEARCH (8)
736+
737+
// is the page not yet used up to its reserved space?
738+
static bool mi_page_is_expandable(const mi_page_t* page) {
739+
mi_assert_internal(page != NULL);
740+
mi_assert_internal(page->capacity <= page->reserved);
741+
return (page->capacity < page->reserved);
742+
}
743+
744+
712745
// Find a page with free blocks of `page->block_size`.
713746
static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* pq, bool first_try)
714747
{
715748
// search through the pages in "next fit" order
716749
#if MI_STAT
717750
size_t count = 0;
718751
#endif
752+
size_t candidate_count = 0; // we reset this on the first candidate to limit the search
753+
mi_page_t* page_candidate = NULL; // a page with free space
719754
mi_page_t* page = pq->first;
755+
720756
while (page != NULL)
721757
{
722758
mi_page_t* next = page->next; // remember next
723759
#if MI_STAT
724760
count++;
725761
#endif
762+
candidate_count++;
726763

727-
// 0. collect freed blocks by us and other threads
764+
// collect freed blocks by us and other threads
728765
_mi_page_free_collect(page, false);
729766

730-
// 1. if the page contains free blocks, we are done
731-
if (mi_page_immediate_available(page)) {
732-
break; // pick this one
733-
}
767+
#if MI_MAX_CANDIDATE_SEARCH > 1
768+
// search up to N pages for a best candidate
734769

735-
// 2. Try to extend
736-
if (page->capacity < page->reserved) {
737-
mi_page_extend_free(heap, page, heap->tld);
738-
mi_assert_internal(mi_page_immediate_available(page));
739-
break;
770+
// is the local free list non-empty?
771+
const bool immediate_available = mi_page_immediate_available(page);
772+
773+
// if the page is completely full, move it to the `mi_pages_full`
774+
// queue so we don't visit long-lived pages too often.
775+
if (!immediate_available && !mi_page_is_expandable(page)) {
776+
mi_assert_internal(!mi_page_is_in_full(page) && !mi_page_immediate_available(page));
777+
mi_page_to_full(page, pq);
778+
}
779+
else {
780+
// the page has free space, make it a candidate
781+
// we prefer non-expandable pages with high usage as candidates (to reduce commit, and increase chances of free-ing up pages)
782+
if (page_candidate == NULL) {
783+
page_candidate = page;
784+
candidate_count = 0;
785+
}
786+
else if (/* !mi_page_is_expandable(page) && */ page->used >= page_candidate->used) {
787+
page_candidate = page;
788+
}
789+
// if we find a non-expandable candidate, or searched for N pages, return with the best candidate
790+
if (immediate_available || candidate_count > MI_MAX_CANDIDATE_SEARCH) {
791+
mi_assert_internal(page_candidate!=NULL);
792+
break;
793+
}
794+
}
795+
#else
796+
// first-fit algorithm
797+
// If the page contains free blocks, we are done
798+
if (mi_page_immediate_available(page) || mi_page_is_expandable(page)) {
799+
break; // pick this one
740800
}
741801

742-
// 3. If the page is completely full, move it to the `mi_pages_full`
802+
// If the page is completely full, move it to the `mi_pages_full`
743803
// queue so we don't visit long-lived pages too often.
744804
mi_assert_internal(!mi_page_is_in_full(page) && !mi_page_immediate_available(page));
745805
mi_page_to_full(page, pq);
806+
#endif
746807

747808
page = next;
748809
} // for each page
749810

750811
mi_heap_stat_counter_increase(heap, searches, count);
751812

813+
// set the page to the best candidate
814+
if (page_candidate != NULL) {
815+
page = page_candidate;
816+
}
817+
if (page != NULL && !mi_page_immediate_available(page)) {
818+
mi_assert_internal(mi_page_is_expandable(page));
819+
mi_page_extend_free(heap, page, heap->tld);
820+
}
821+
752822
if (page == NULL) {
753823
_mi_heap_collect_retired(heap, false); // perhaps make a page available
754824
page = mi_page_fresh(heap, pq);
@@ -758,18 +828,24 @@ static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* p
758828
}
759829
}
760830
else {
761-
mi_assert(pq->first == page);
831+
// move the page to the front of the queue
832+
mi_page_queue_move_to_front(heap, pq, page);
762833
page->retire_expire = 0;
834+
// _mi_heap_collect_retired(heap, false); // update retire counts; note: increases rss on MemoryLoad bench so don't do this
763835
}
764836
mi_assert_internal(page == NULL || mi_page_immediate_available(page));
837+
838+
765839
return page;
766840
}
767841

768842

769843

770844
// Find a page with free blocks of `size`.
771845
static inline mi_page_t* mi_find_free_page(mi_heap_t* heap, size_t size) {
772-
mi_page_queue_t* pq = mi_page_queue(heap,size);
846+
mi_page_queue_t* pq = mi_page_queue(heap, size);
847+
848+
// check the first page: we even do this with candidate search or otherwise we re-search every time
773849
mi_page_t* page = pq->first;
774850
if (page != NULL) {
775851
#if (MI_SECURE>=3) // in secure mode, we extend half the time to increase randomness
@@ -788,6 +864,7 @@ static inline mi_page_t* mi_find_free_page(mi_heap_t* heap, size_t size) {
788864
return page; // fast path
789865
}
790866
}
867+
791868
return mi_page_queue_find_free_ex(heap, pq, true);
792869
}
793870

0 commit comments

Comments
 (0)