@@ -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
363363static 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
408429void _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`.
713746static 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`.
771845static 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