[PATCH] malloc: Optimize the madvise behaviour on the main heap

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Fri Nov 14 16:27:50 GMT 2025



On 09/11/25 04:29, Dev Jain wrote:
> Linux handles virtual memory in Virtual Memory Areas (VMAs). The
> madvise(MADV_HUGEPAGE) call works on a VMA granularity, which sets the
> VM_HUGEPAGE flag on the VMA. Therefore, if we can guarantee that a VMA
> has been marked with VM_HUGEPAGE already, then we do not need to call
> madvise() on that VMA again.
> 
> For mp_.thp_pagesize != 0, currently we align the new brk to the thp size.
> This means that after the first extension, all such brk extensions are
> guaranteed to produce an extension size >= thp size: madvise_thp() will
> invoke the madvise() syscall only if size >= thp size, and the other
> condition is related to the sysctl setting, wherein mp_.thp_mode will be
> same throughout the lifetime of the process. Therefore, currently we invoke
> the madvise() syscall on the heap on each extension, which is unnecessary.
> 
> First, pass the total heap size, instead of the extension size, to
> madvise_thp: Linux does not care about the size passed, in case the
> madvise() syscall is invoked with MADV_HUGEPAGE flag, because the flag
> will be set on the entire VMA, no matter for what portion of the VMA
> the syscall is invoked.
> 
> This enables us to do the following: if the old heap size >= thp size,
> we can guarantee that madvise() was invoked on one of the previous
> extensions of the heap. So, avoid making the syscall in this case.
> 
> The tricky part is computing the size of the heap, i.e the current program
> break minus the initial program break. In case the first ever attempt at
> extending the break fails, mp_.sbrk_base will be set to an mmapped address.
> Therefore, we need some other way of remembering the initial location
> of the program break. We can reuse some code for this: MORECORE (0), when
> invoked for the first time ever, will give us the initial program break.
> ---
> The patch applies on 259adb087dd9. Built on Aarch64, all malloc tests pass.

I am not sure if the calculation of 

> 
>  malloc/malloc.c | 23 +++++++++++++++++++++--
>  1 file changed, 21 insertions(+), 2 deletions(-)
> 
> diff --git a/malloc/malloc.c b/malloc/malloc.c
> index 0b21bdf1bd..277fb9e9ec 100644
> --- a/malloc/malloc.c
> +++ b/malloc/malloc.c
> @@ -1938,6 +1938,12 @@ struct malloc_par
>    /* First address handed out by MORECORE/sbrk.  */
>    char *sbrk_base;
>  
> +  /* The initial location of program break. This will most likely be equal
> +     to sbrk_base; in case the first ever extension attempt of brk fails,
> +     sbrk_base will point to an mmapped address (see sysmalloc_mmap_fallback),
> +     in which case these two values will not be equal.  */
> +  char *init_sbrk_base;
> +
>  #if USE_TCACHE
>    /* Maximum number of small buckets to use.  */
>    size_t tcache_small_bins;
> @@ -2667,6 +2673,9 @@ sysmalloc (INTERNAL_SIZE_T nb, mstate av)
>        if (__glibc_unlikely (mp_.thp_pagesize != 0))
>  	{
>  	  uintptr_t lastbrk = (uintptr_t) MORECORE (0);
> +	  if (mp_.init_sbrk_base == NULL)
> +	    mp_.init_sbrk_base = (char *) lastbrk;
> +
>  	  uintptr_t top = ALIGN_UP (lastbrk + size, mp_.thp_pagesize);
>  	  size = top - lastbrk;
>  	}
> @@ -2682,8 +2691,18 @@ sysmalloc (INTERNAL_SIZE_T nb, mstate av)
>        if ((ssize_t) size > 0)
>          {
>            brk = (char *) (MORECORE ((long) size));
> -	  if (brk != (char *) (MORECORE_FAILURE))
> -	    madvise_thp (brk, size);
> +	  if (brk != (char *) (MORECORE_FAILURE)) {
> +	    size_t old_size = (size_t) (brk - mp_.init_sbrk_base);
> +
> +	    /*
> +	      If heap already marked with MADV_HUGEPAGE, skip madvise(). Note
> +	      that, we don't need to check mp_.init_sbrk_base != NULL; if it
> +	      is NULL, it implies that mp_.thp_pagesize == 0, in which case
> +	      madvise_thp() will not invoke madvise().
> +	     */
> +	    if (old_size < mp_.thp_pagesize)
> +	    madvise_thp (brk, old_size + size);

I am not sure if this calculation is fully correct, with a simple testcase:

$ cat t.c
#include <stdlib.h>
#include <pthread.h>

static void *tf (void* arg)
{
  for (int i = 0; i < 1024; i++)
    malloc (4096);
  return NULL;
}

int main ()
{
  for (int i = 0; i < 1024; i++)
    malloc (4096);

  pthread_t t;
  pthread_create (&t, NULL, tf, NULL);
  pthread_join (t, NULL);
}
$ strace -e madvise -f -E GLIBC_TUNABLES=glibc.malloc.hugetlb=1 elf/ld.so --library-path . ./t
madvise(0xaaaade400000, 3354624, MADV_HUGEPAGE) = -1 ENOMEM (Cannot allocate memory)
madvise(0xf7fa28ed0000, 65536, 0x66 /* MADV_??? */) = 0
strace: Process 313335 attached
[pid 313335] madvise(0xf7fa24000000, 2097152, MADV_HUGEPAGE) = 0
[pid 313335] madvise(0xf7fa28ed0000, 8314880, MADV_DONTNEED) = 0
[pid 313335] +++ exited with 0 +++
+++ exited with 0 +++

Where without this patch:

$ strace -e madvise -f -E GLIBC_TUNABLES=glibc.malloc.hugetlb=1 ./t
madvise(0xc3da42fb9000, 2097152, MADV_HUGEPAGE) = 0
madvise(0xc3da43200000, 2097152, MADV_HUGEPAGE) = 0
strace: Process 313347 attached
[pid 313347] madvise(0xfa19510a0000, 8314880, MADV_DONTNEED) = 0
[pid 313347] +++ exited with 0 +++
+++ exited with 0 +++


The second madvise call 'madvise(0xf7fa28ed0000, 65536, 0x66 /* MADV_??? */)'
is indeed even bogus.  With gdb the value passed on the syscall are indeed:

(gdb) catch syscall madvise
(gdb) r
[...]
(gdb) i r
x0             0xfffffffffffffff4  -12
x1             0x355000            3493888
x2             0xe                 14
[...]

> +	  }
>            LIBC_PROBE (memory_sbrk_more, 2, brk, size);
>          }
>  



More information about the Libc-alpha mailing list