[PATCH 1/6] malloc: Move mmap code out of __libc_free hotpath

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Fri Apr 11 19:21:39 GMT 2025



On 31/03/25 09:54, Wilco Dijkstra wrote:
> 
> Currently __libc_free checks for a freed mmap chunk in the fast path.
> Also errno is always saved and restored to preserve it.  Since mmap chunks
> are larger than the largest tcache chunk, it is safe to delay this and
> handle tcache, smallbin and medium bin blocks first.  Move saving of errno
> to cases that actually need it.  Remove a safety check that fails on mmap
> chunks and a check that mmap chunks cannot be added to tcache. 
> 
> Performance of bench-malloc-thread improves by 9.2% for 1 thread and
> 6.9% for 32 threads on Neoverse V2.
> 
> Passes regress, OK for commit?
> 
> ---
> 
> diff --git a/malloc/malloc.c b/malloc/malloc.c
> index a0bc733482532ce34684d0357cb9076b03ac8a52..fc2f751174d88d1029f04958ca1ebf37ca48bd87 100644
> --- a/malloc/malloc.c
> +++ b/malloc/malloc.c
> @@ -3316,6 +3316,14 @@ tcache_init(void)
>    if (tcache_shutting_down)
>      return;
>  
> +  /* Check minimum mmap chunk is larger than max tcache size.  This means
> +     mmap chunks with their different layout are never added to tcache.  */
> +  if (MAX_TCACHE_SIZE >= GLRO (dl_pagesize) / 2)
> +    malloc_printerr ("max tcache size too large");

Ok. I think we can eventually make it an _Static_assert once we have the pagesize.h
with the max and min support page sizes values.

And checking on this code, it makes me wonder if glibc.malloc.tcache_max tunable
is really worth. Without it, I think we can optimize tcache_available code
generation since the mp_.tcache_bins could be replaced by a constant.

> +
> +  /* Preserve errno when called from free() - _int_malloc may corrupt it.  */
> +  int err = errno;
> +
>    arena_get (ar_ptr, bytes);
>    victim = _int_malloc (ar_ptr, bytes);
>    if (!victim && ar_ptr != NULL)
> @@ -3324,10 +3332,11 @@ tcache_init(void)
>        victim = _int_malloc (ar_ptr, bytes);
>      }
>  
> -

Spurious line removal.

>    if (ar_ptr != NULL)
>      __libc_lock_unlock (ar_ptr->mutex);
>  
> +  __set_errno (err);
> +
>    /* In a low memory situation, we may not be able to allocate memory
>       - in which case, we just keep trying later.  However, we
>       typically do this very early, so either there is sufficient
> @@ -3453,37 +3462,15 @@ __libc_free (void *mem)
>    if (__glibc_unlikely (mtag_enabled))
>      *(volatile char *)mem;
>  
> -  int err = errno;
> -
>    p = mem2chunk (mem);
>  
> -  if (chunk_is_mmapped (p))                       /* release mmapped memory. */
> -    {
> -      /* See if the dynamic brk/mmap threshold needs adjusting.
> -	 Dumped fake mmapped chunks do not affect the threshold.  */
> -      if (!mp_.no_dyn_threshold
> -          && chunksize_nomask (p) > mp_.mmap_threshold
> -          && chunksize_nomask (p) <= DEFAULT_MMAP_THRESHOLD_MAX)
> -        {
> -          mp_.mmap_threshold = chunksize (p);
> -          mp_.trim_threshold = 2 * mp_.mmap_threshold;
> -          LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
> -                      mp_.mmap_threshold, mp_.trim_threshold);
> -        }
> -      munmap_chunk (p);
> -    }
> -  else
> -    {
> -      MAYBE_INIT_TCACHE ();
> -
> -      /* Mark the chunk as belonging to the library again.  */
> -      (void)tag_region (chunk2mem (p), memsize (p));
> +  MAYBE_INIT_TCACHE ();
>  
> -      ar_ptr = arena_for_chunk (p);
> -      _int_free (ar_ptr, p, 0);
> -    }
> +  /* Mark the chunk as belonging to the library again.  */
> +  (void)tag_region (chunk2mem (p), memsize (p));

I don't think we strictly need the return cast.

>  
> -  __set_errno (err);
> +  ar_ptr = arena_for_chunk (p);
> +  _int_free (ar_ptr, p, 0);
>  }
>  libc_hidden_def (__libc_free)
>  
> @@ -4570,9 +4557,8 @@ _int_free_check (mstate av, mchunkptr p, INTERNAL_SIZE_T size)
>    if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
>        || __builtin_expect (misaligned_chunk (p), 0))
>      malloc_printerr ("free(): invalid pointer");
> -  /* We know that each chunk is at least MINSIZE bytes in size or a
> -     multiple of MALLOC_ALIGNMENT.  */
> -  if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
> +  /* We know that each chunk is at least MINSIZE bytes.  */
> +  if (__glibc_unlikely (size < MINSIZE))
>      malloc_printerr ("free(): invalid size");
>  
>    check_inuse_chunk (av, p);
> @@ -4669,6 +4655,9 @@ _int_free_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T size, int have_lock)
>  
>    else if (!chunk_is_mmapped(p)) {
>  
> +    /* Preserve errno in case block merging results in munmap.  */
> +    int err = errno;
> +
>      /* If we're single-threaded, don't lock the arena.  */
>      if (SINGLE_THREAD_P)
>        have_lock = true;
> @@ -4680,13 +4669,33 @@ _int_free_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T size, int have_lock)
>  
>      if (!have_lock)
>        __libc_lock_unlock (av->mutex);
> +
> +    __set_errno (err);
>    }
>    /*
>      If the chunk was allocated via mmap, release via munmap().
>    */
>  
>    else {
> +
> +    /* Preserve errno in case munmap sets it.  */
> +    int err = errno;
> +
> +    /* See if the dynamic brk/mmap threshold needs adjusting.
> +       Dumped fake mmapped chunks do not affect the threshold.  */
> +    if (!mp_.no_dyn_threshold
> +        && chunksize_nomask (p) > mp_.mmap_threshold
> +        && chunksize_nomask (p) <= DEFAULT_MMAP_THRESHOLD_MAX)
> +      {
> +        mp_.mmap_threshold = chunksize (p);
> +        mp_.trim_threshold = 2 * mp_.mmap_threshold;
> +        LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
> +		    mp_.mmap_threshold, mp_.trim_threshold);
> +      }
> +
>      munmap_chunk (p);
> +
> +    __set_errno (err);
>    }
>  }
>  

The rest look ok. I think DJ also give this a RB.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>



More information about the Libc-alpha mailing list