@@ -93,16 +93,23 @@ _PyMem_RawFree(void *Py_UNUSED(ctx), void *ptr)
9393static bool count_blocks (const mi_heap_t * , const mi_heap_area_t * ,
9494 void * , size_t , void * );
9595
96- // mimalloc v3 delays returning freed memory to the OS for much longer than v2
97- // did (1000 ms purge delay and 4x arena multiplier, vs 10 ms and 10x), which
98- // noticeably increases peak RSS. Restore the v2 timings, which have no
99- // measurable speed cost. MIMALLOC_PURGE_DELAY and MIMALLOC_ARENA_PURGE_MULT
100- // still take precedence.
96+ // mimalloc v3 keeps freed memory resident much longer than v2 did, which
97+ // noticeably increases peak RSS of short-running processes:
98+ // - All purges use purge_delay * arena_purge_mult (1000 ms * 4 by default),
99+ // while v2 purged freed pages after just purge_delay (10 ms).
100+ // - Delayed purges only release freed ranges of at least the minimal purge
101+ // size, which is 2 MiB when transparent huge pages are detected.
102+ // - Abandoned full pages hold memory that is not reused promptly.
103+ // Use v2-like timings, purge freed ranges at page granularity, and retain full
104+ // pages in their owning thread. None of these has a measurable speed cost.
105+ // The corresponding MIMALLOC_* environment variables still take precedence.
101106static void
102107set_mimalloc_defaults (void )
103108{
104109 mi_option_set_default (mi_option_purge_delay , 10 );
105- mi_option_set_default (mi_option_arena_purge_mult , 10 );
110+ mi_option_set_default (mi_option_arena_purge_mult , 1 );
111+ mi_option_set_default (mi_option_arena_purge_min_size , 64 ); // KiB
112+ mi_option_set_default (mi_option_page_full_retain , -1 );
106113}
107114
108115#ifdef Py_GIL_DISABLED
@@ -194,22 +201,9 @@ _PyMem_InitMimalloc(PyInterpreterState *interp)
194201{
195202 set_mimalloc_defaults ();
196203
197- #ifdef Py_GIL_DISABLED
198- assert (page -> qsbr_node .next == NULL );
199- if (page -> qsbr_goal != 0 ) {
200- if (mi_page_all_free (page )) {
201- assert (page -> qsbr_node .next == NULL );
202- _PyThreadStateImpl * tstate = tstate_from_heap (mi_page_heap (page ));
203- // gh-159462: the owning tstate may differ from the current
204- // one, but the heap is always owned by the current OS thread,
205- // so the page list is not shared with other OS threads.
206- assert (mi_page_heap (page )-> thread_id == _Py_ThreadId ());
207- page -> retire_expire = 0 ;
208- llist_insert_tail (& tstate -> mimalloc .page_list , & page -> qsbr_node );
209- }
210- else {
211- page -> qsbr_goal = 0 ;
212- }
204+ size_t base_offset = offsetof(PyObject , ob_type );
205+ if (_PyMem_DebugEnabled ()) {
206+ base_offset += 2 * sizeof (size_t );
213207 }
214208 size_t debug_offsets [_Py_MIMALLOC_HEAP_COUNT ] = {
215209 [_Py_MIMALLOC_HEAP_OBJECT ] = base_offset ,
@@ -231,7 +225,6 @@ _PyMem_InitMimalloc(PyInterpreterState *interp)
231225 }
232226 mimalloc_init_qsbr = NULL ;
233227 return 0 ;
234- #endif
235228}
236229
237230void
0 commit comments