@@ -225,6 +225,9 @@ void _mi_page_free_collect(mi_page_t* page, bool force) {
225225
226226 // and the local free list
227227 if (page -> local_free != NULL ) {
228+ // any previous QSBR goals are no longer valid because we reused the page
229+ _PyMem_mi_page_clear_qsbr (page );
230+
228231 if mi_likely (page -> free == NULL ) {
229232 // usual case
230233 page -> free = page -> local_free ;
@@ -267,6 +270,7 @@ void _mi_page_reclaim(mi_heap_t* heap, mi_page_t* page) {
267270 // TODO: push on full queue immediately if it is full?
268271 mi_page_queue_t * pq = mi_page_queue (heap , mi_page_block_size (page ));
269272 mi_page_queue_push (heap , pq , page );
273+ _PyMem_mi_page_reclaimed (page );
270274 mi_assert_expensive (_mi_page_is_valid (page ));
271275}
272276
@@ -383,6 +387,13 @@ void _mi_page_abandon(mi_page_t* page, mi_page_queue_t* pq) {
383387
384388 mi_heap_t * pheap = mi_page_heap (page );
385389
390+ #ifdef Py_GIL_DISABLED
391+ if (page -> qsbr_node .next != NULL ) {
392+ // remove from QSBR queue, but keep the goal
393+ llist_remove (& page -> qsbr_node );
394+ }
395+ #endif
396+
386397 // remove from our page list
387398 mi_segments_tld_t * segments_tld = & pheap -> tld -> segments ;
388399 mi_page_queue_remove (pq , page );
@@ -417,6 +428,11 @@ void _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force) {
417428
418429 mi_heap_t * heap = mi_page_heap (page );
419430
431+ #ifdef Py_GIL_DISABLED
432+ mi_assert_internal (page -> qsbr_goal == 0 );
433+ mi_assert_internal (page -> qsbr_node .next == NULL );
434+ #endif
435+
420436 // remove from the page list
421437 // (no need to do _mi_heap_delayed_free first as all blocks are already free)
422438 mi_segments_tld_t * segments_tld = & heap -> tld -> segments ;
@@ -444,6 +460,9 @@ void _mi_page_retire(mi_page_t* page) mi_attr_noexcept {
444460
445461 mi_page_set_has_aligned (page , false);
446462
463+ // any previous QSBR goals are no longer valid because we reused the page
464+ _PyMem_mi_page_clear_qsbr (page );
465+
447466 // don't retire too often..
448467 // (or we end up retiring and re-allocating most of the time)
449468 // NOTE: refine this more: we should not retire if this
@@ -465,7 +484,7 @@ void _mi_page_retire(mi_page_t* page) mi_attr_noexcept {
465484 return ; // dont't free after all
466485 }
467486 }
468- _mi_page_free (page , pq , false);
487+ _PyMem_mi_page_maybe_free (page , pq , false);
469488}
470489
471490// free retired pages: we don't need to look at the entire queues
@@ -480,7 +499,10 @@ void _mi_heap_collect_retired(mi_heap_t* heap, bool force) {
480499 if (mi_page_all_free (page )) {
481500 page -> retire_expire -- ;
482501 if (force || page -> retire_expire == 0 ) {
483- _mi_page_free (pq -> first , pq , force );
502+ #ifdef Py_GIL_DISABLED
503+ mi_assert_internal (page -> qsbr_goal == 0 );
504+ #endif
505+ _PyMem_mi_page_maybe_free (page , pq , force );
484506 }
485507 else {
486508 // keep retired, update min/max
@@ -661,6 +683,7 @@ static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi
661683 // set fields
662684 mi_page_set_heap (page , heap );
663685 page -> tag = heap -> tag ;
686+ page -> use_qsbr = heap -> page_use_qsbr ;
664687 page -> debug_offset = heap -> debug_offset ;
665688 page -> xblock_size = (block_size < MI_HUGE_BLOCK_SIZE ? (uint32_t )block_size : MI_HUGE_BLOCK_SIZE ); // initialize before _mi_segment_page_start
666689 size_t page_size ;
@@ -691,6 +714,10 @@ static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi
691714 mi_assert_internal (page -> xthread_free == 0 );
692715 mi_assert_internal (page -> next == NULL );
693716 mi_assert_internal (page -> prev == NULL );
717+ #ifdef Py_GIL_DISABLED
718+ mi_assert_internal (page -> qsbr_goal == 0 );
719+ mi_assert_internal (page -> qsbr_node .next == NULL );
720+ #endif
694721 mi_assert_internal (page -> retire_expire == 0 );
695722 mi_assert_internal (!mi_page_has_aligned (page ));
696723 #if (MI_PADDING || MI_ENCODE_FREELIST )
@@ -750,6 +777,7 @@ static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* p
750777 mi_heap_stat_counter_increase (heap , searches , count );
751778
752779 if (page == NULL ) {
780+ _PyMem_mi_heap_collect_qsbr (heap ); // some pages might be safe to free now
753781 _mi_heap_collect_retired (heap , false); // perhaps make a page available?
754782 page = mi_page_fresh (heap , pq );
755783 if (page == NULL && first_try ) {
@@ -760,6 +788,7 @@ static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* p
760788 else {
761789 mi_assert (pq -> first == page );
762790 page -> retire_expire = 0 ;
791+ _PyMem_mi_page_clear_qsbr (page );
763792 }
764793 mi_assert_internal (page == NULL || mi_page_immediate_available (page ));
765794 return page ;
@@ -785,6 +814,7 @@ static inline mi_page_t* mi_find_free_page(mi_heap_t* heap, size_t size) {
785814
786815 if (mi_page_immediate_available (page )) {
787816 page -> retire_expire = 0 ;
817+ _PyMem_mi_page_clear_qsbr (page );
788818 return page ; // fast path
789819 }
790820 }
@@ -878,6 +908,7 @@ static mi_page_t* mi_find_page(mi_heap_t* heap, size_t size, size_t huge_alignme
878908 return NULL ;
879909 }
880910 else {
911+ _PyMem_mi_heap_collect_qsbr (heap );
881912 return mi_large_huge_page_alloc (heap ,size ,huge_alignment );
882913 }
883914 }
0 commit comments