[glibc/fw/malloc-batch-unsorted-merge-llm-2] Deferred-scan batch freeing, trying to group arena operations
Florian Weimer via Glibc-cvs <[email protected]> Tue, 30 Jun 2026 08:24:22 +0000 (GMT)
| Newsgroups | gmane.comp.lib.glibc.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=7d730e9283aab37cb023da1dbcdf16b5b4255094 commit 7d730e9283aab37cb023da1dbcdf16b5b4255094 Author: Florian Weimer <[email protected]> Date: Sun Jun 28 13:25:43 2026 +0200 Deferred-scan batch freeing, trying to group arena operations Assisted-by: LLM Diff: --- malloc/malloc.c | 126 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 75 insertions(+), 51 deletions(-) diff --git a/malloc/malloc.c b/malloc/malloc.c index d6500e8ba5..e4386b7214 100644 --- a/malloc/malloc.c +++ b/malloc/malloc.c @@ -3010,28 +3010,16 @@ tcache_free_init (void *mem) /* Adjacent tcache entries are merged locally before touching the heap, so that _int_free_merge_chunk is called once for the whole block instead of inserting and immediately unlinking individual chunks. - The arena lock acquisition is delayed until the first flush and - kept between flushes to the same arena. */ -static __always_inline void -__libc_free_batched_loop (bool do_lock, mchunkptr p, INTERNAL_SIZE_T size, - tcache_perthread_struct *tc, size_t tc_idx) + Used for the single-threaded path where all chunks are in + main_arena. */ +static void +__libc_free_batched_single (mchunkptr p, INTERNAL_SIZE_T size, + tcache_perthread_struct *tc, size_t tc_idx) { - /* Empty half of the tcache, for a hysteresis effect. */ unsigned int to_free = mp_.tcache_count / 2; INTERNAL_SIZE_T chunk_size = size; + mstate av = &main_arena; - /* The currently locked arena. */ - mstate av; - if (do_lock) - /* Lazily acquire the lock before the first _int_free_merge_chunk call. */ - av = NULL; - else - /* No locking. Chunks are always in the main arena. */ - av = &main_arena; - - /* Accumulate adjacent chunks from the tcache into [p, p+size) - without modifying the heap. Flush to the heap when a - non-adjacent chunk is encountered. */ while (tc->entries[tc_idx] != NULL && to_free > 0) { void *mem = tcache_get_n (tc, tc_idx, &tc->entries[tc_idx], false); @@ -3039,32 +3027,15 @@ __libc_free_batched_loop (bool do_lock, mchunkptr p, INTERNAL_SIZE_T size, if ((char *) q + chunk_size == (char *) p) { - /* q is immediately before our block, extend backward. */ p = q; size += chunk_size; } else if ((char *) p + size == (char *) q) { - /* q is immediately after our block, extend forward. */ size += chunk_size; } else { - /* Not adjacent. Flush the accumulated block. */ - if (do_lock) - { - mstate new_av = arena_for_chunk (p); - if (new_av != av) - { - if (av != NULL) - __libc_lock_unlock (av->mutex); - av = new_av; - __libc_lock_lock (av->mutex); - } - } -#ifdef MALLOC_DEBUG - set_head (p, size | (chunksize_nomask (p) & SIZE_BITS)); -#endif _int_free_merge_chunk (av, p, size); p = q; size = chunk_size; @@ -3072,24 +3043,77 @@ __libc_free_batched_loop (bool do_lock, mchunkptr p, INTERNAL_SIZE_T size, to_free--; } - /* Flush the remaining accumulated block. */ - if (do_lock) + _int_free_merge_chunk (av, p, size); +} + +/* Multi-threaded batched free. Pop chunks from the tcache and free + them to their arenas. Chunks matching the current arena are freed + immediately (interleaving _int_free_merge_chunk with tcache pops to + hide pointer-chase latency). Chunks from other arenas are deferred + to a small buffer and processed afterwards, grouped by arena. */ +static void +__libc_free_batched_multi (mchunkptr p, INTERNAL_SIZE_T size, + tcache_perthread_struct *tc, size_t tc_idx) +{ + INTERNAL_SIZE_T chunk_size = size; + unsigned int to_free = mp_.tcache_count / 2; + if (to_free > 16) + to_free = 16; + + /* Lock the trigger chunk's arena and free it. */ + mstate av = arena_for_chunk (p); + __libc_lock_lock (av->mutex); + _int_free_merge_chunk (av, p, chunk_size); + + /* Deferred chunks from non-current arenas. */ + mchunkptr deferred_chunks[16]; + mstate deferred_arenas[16]; + unsigned int deferred_count = 0; + + /* Pop from tcache. Free same-arena chunks immediately (the + _int_free_merge_chunk call between pops hides the latency of + the next tcache pointer chase). Defer arena mismatches. */ + while (tc->entries[tc_idx] != NULL && to_free > 0) { - mstate new_av = arena_for_chunk (p); - if (new_av != av) + void *mem = tcache_get_n (tc, tc_idx, &tc->entries[tc_idx], false); + mchunkptr q = mem2chunk (mem); + mstate qav = arena_for_chunk (q); + if (qav == av) + _int_free_merge_chunk (av, q, chunk_size); + else { - if (av != NULL) - __libc_lock_unlock (av->mutex); - av = new_av; - __libc_lock_lock (av->mutex); + deferred_chunks[deferred_count] = q; + deferred_arenas[deferred_count] = qav; + deferred_count++; } + to_free--; + } + + __libc_lock_unlock (av->mutex); + + /* Process deferred chunks grouped by arena. */ + for (unsigned int i = 0; i < deferred_count; i++) + { + if (deferred_chunks[i] == NULL) + continue; + + av = deferred_arenas[i]; + __libc_lock_lock (av->mutex); + + _int_free_merge_chunk (av, deferred_chunks[i], chunk_size); + deferred_chunks[i] = NULL; + + for (unsigned int j = i + 1; j < deferred_count; j++) + { + if (deferred_chunks[j] != NULL && deferred_arenas[j] == av) + { + _int_free_merge_chunk (av, deferred_chunks[j], chunk_size); + deferred_chunks[j] = NULL; + } + } + + __libc_lock_unlock (av->mutex); } -#ifdef MALLOC_DEBUG - set_head (p, size | (chunksize_nomask (p) & SIZE_BITS)); -#endif - _int_free_merge_chunk (av, p, size); - if (do_lock) - __libc_lock_unlock (av->mutex); } /* Deallocate half of the tcache entries into arenas, to amortize the @@ -3104,9 +3128,9 @@ __libc_free_batched (mchunkptr p, INTERNAL_SIZE_T size, return malloc_printerr_tail ("free(): invalid size (batch)"); if (SINGLE_THREAD_P) - __libc_free_batched_loop (false, p, size, tc, tc_idx); + __libc_free_batched_single (p, size, tc, tc_idx); else - __libc_free_batched_loop (true, p, size, tc, tc_idx); + __libc_free_batched_multi (p, size, tc, tc_idx); } void