[glibc/fw/malloc-batch-unsorted-merge-llm-16] Surface-level coalescing in __libc_free_batched_loop
Florian Weimer via Glibc-cvs <[email protected]> Tue, 30 Jun 2026 17:32:15 +0000 (GMT)
| Newsgroups | gmane.comp.lib.glibc.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=8727aa7d8ee046faefad989ec12f91140a7f3daa commit 8727aa7d8ee046faefad989ec12f91140a7f3daa Author: Florian Weimer <[email protected]> Date: Sun Jun 28 12:37:18 2026 +0200 Surface-level coalescing in __libc_free_batched_loop Assisted-by: LLM Diff: --- malloc/malloc.c | 84 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 19 deletions(-) diff --git a/malloc/malloc.c b/malloc/malloc.c index 99ba94454e..3b92cb6e04 100644 --- a/malloc/malloc.c +++ b/malloc/malloc.c @@ -3007,41 +3007,87 @@ tcache_free_init (void *mem) __libc_free (mem); } -/* If the arena does not change between chunks, keep the lock. */ -static inline void -__libc_free_batched_loop (bool do_lock, mstate av, mchunkptr p, INTERNAL_SIZE_T size, +/* 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) { /* Empty half of the tcache, for a hysteresis effect. */ unsigned int to_free = mp_.tcache_count / 2; + INTERNAL_SIZE_T chunk_size = size; + /* The currently locked arena. */ + mstate av; if (do_lock) - __libc_lock_lock (av->mutex); - - _int_free_merge_chunk (av, p, size); + /* 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); - p = mem2chunk (mem); - size = chunksize (p); + mchunkptr q = mem2chunk (mem); - /* Lock a different arena if necessary. */ - if (do_lock) + 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 { - mstate chunk_av = arena_for_chunk (p); - if (chunk_av != av) + /* Not adjacent. Flush the accumulated block. */ + if (do_lock) { - __libc_lock_unlock (av->mutex); - av = chunk_av; - __libc_lock_lock (av->mutex); + 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; } - - _int_free_merge_chunk (av, p, size); to_free--; } + /* Flush the remaining 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); if (do_lock) __libc_lock_unlock (av->mutex); } @@ -3058,9 +3104,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, &main_arena, p, size, tc, tc_idx); + __libc_free_batched_loop (false, p, size, tc, tc_idx); else - __libc_free_batched_loop (true, arena_for_chunk (p), p, size, tc, tc_idx); + __libc_free_batched_loop (true, p, size, tc, tc_idx); } void