[patch v3] malloc: avoid need for tcache == NULL checks

Florian Weimer fweimer@redhat.com
Wed Sep 3 06:26:31 GMT 2025


* DJ Delorie:

> +/* TCACHE is never NULL; it's either "live" or points to one of the
> +   above dummy entries.  The dummy entries are all zero so act like an
> +   empty/unusable tcache.  */
> +static __thread tcache_perthread_struct *tcache = (tcache_perthread_struct *) &__tcache_dummy.inactive;

Line is too long.

>  /* Initialize tcache.  In the rare case there isn't any memory available,
>     later calls will retry initialization.  */
>  static void
> -tcache_init (void)
> +tcache_init (mstate av)
>  {
> -  if (tcache_shutting_down)
> +  /* 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_SMALL_SIZE >= GLRO (dl_pagesize) / 2)
> +    malloc_printerr ("max tcache size too large");

Uhm, this was removed in:

commit 614cfd0f8a2820aed54f9745077c7da0e6643bac
Author: Wilco Dijkstra <wilco.dijkstra@arm.com>
Date:   Fri Aug 8 14:11:13 2025 +0000

    malloc: Change mmap chunk layout
    
    Change the mmap chunk layout to be identical to a normal chunk.  This makes it
    safe for tcache to hold mmap chunks and simplifies size calculations in
    memsize and musable.  Add mmap_base() and mmap_size() macros to simplify code.
    
    Reviewed-by: Cupertino Miranda <cupertino.miranda@oracle.com>


> @@ -3510,8 +3550,11 @@ __libc_free (void *mem)
>      return malloc_printerr_tail ("free(): invalid pointer");
>  
>  #if USE_TCACHE
> -  if (__glibc_likely (size < mp_.tcache_max_bytes && tcache != NULL))
> +  if (__glibc_likely (size < mp_.tcache_max_bytes))
>      {
> +      if (__glibc_unlikely (tcache_inactive ()))
> +	tcache_init (NULL);
> +
>        /* Check to see if it's already in the tcache.  */
>        tcache_entry *e = (tcache_entry *) chunk2mem (p);

This should come after we checked if there was room in tcache and found
none.  And I hope that


  if (__glibc_unlikely (tcache_inactive ()))
    {
      tcache_init (NULL);
      return __libc_free (mem);
    }

generates good code.  We don't want GCC to put NOPs into the middle of
free, to align a loop that pretty much always is only exected once.

> @@ -3896,9 +3939,6 @@ __libc_calloc (size_t n, size_t elem_size)
>  
>    if (nb < mp_.tcache_max_bytes)
>      {
> -      if (__glibc_unlikely (tcache == NULL))
> -	return tcache_calloc_init (bytes);
> -
>        size_t tc_idx = csize2tidx (nb);
>  
>        if (__glibc_unlikely (tc_idx < TCACHE_SMALL_BINS))
> @@ -4030,10 +4070,14 @@ _int_malloc (mstate av, size_t bytes)
>  	      /* While we're here, if we see other chunks of the same size,
>  		 stash them in the tcache.  */
>  	      size_t tc_idx = csize2tidx (nb);
> -	      if (tcache != NULL && tc_idx < mp_.tcache_small_bins)
> +	      if (tc_idx < mp_.tcache_small_bins)
>  		{
>  		  mchunkptr tc_victim;
>  
> +#if USE_TCACHE
> +		  if (__glibc_unlikely (tcache_inactive ()))
> +		    tcache_init (av);
> +#endif
>  		  /* While bin not empty and tcache not full, copy chunks.  */
>  		  while (tcache->num_slots[tc_idx] != 0 && (tc_victim = *fb) != NULL)
>  		    {

I don't think you can call into malloc at this point because it may
invalidate the bin contents.  So the while loop should be in the else
part of the if (with a comment that tcache_init sets up the cache for
future calls).

> @@ -4090,10 +4134,14 @@ _int_malloc (mstate av, size_t bytes)
>  	  /* While we're here, if we see other chunks of the same size,
>  	     stash them in the tcache.  */
>  	  size_t tc_idx = csize2tidx (nb);
> -	  if (tcache != NULL && tc_idx < mp_.tcache_small_bins)
> +	  if (tc_idx < mp_.tcache_small_bins)
>  	    {
>  	      mchunkptr tc_victim;
>  
> +#if USE_TCACHE
> +	      if (__glibc_unlikely (tcache_inactive ()))
> +		tcache_init (av);
> +#endif
>  	      /* While bin not empty and tcache not full, copy chunks over.  */
>  	      while (tcache->num_slots[tc_idx] != 0
>  		     && (tc_victim = last (bin)) != bin)

Likely same problem here.

> @@ -4152,7 +4200,7 @@ _int_malloc (mstate av, size_t bytes)
>  #if USE_TCACHE
>    INTERNAL_SIZE_T tcache_nb = 0;
>    size_t tc_idx = csize2tidx (nb);
> -  if (tcache != NULL && tc_idx < mp_.tcache_small_bins)
> +  if (tc_idx < mp_.tcache_small_bins)
>      tcache_nb = nb;
>    int return_cached = 0;
>  
> @@ -4230,6 +4278,8 @@ _int_malloc (mstate av, size_t bytes)
>                if (av != &main_arena)
>  		set_non_main_arena (victim);
>  #if USE_TCACHE
> +	      if (__glibc_unlikely (tcache_inactive ()))
> +		tcache_init (av);
>  	      /* Fill cache first, return to user only if cache fills.
>  		 We may return one of these chunks later.  */
>  	      if (tcache_nb > 0

And here.  The tcache_init call should probably on the else branch that
returns p below.

Thanks,
Florian



More information about the Libc-alpha mailing list