[PATCH] malloc: Optimize the madvise behaviour on the main heap
Dev Jain
dev.jain@arm.com
Tue Dec 2 05:18:29 GMT 2025
On 09/11/25 12:59 pm, 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.
> ---
I am dropping this patch: instead of this, I wrote a trivial patch maintaining
mp_.brk_main_heap_madvised, and using this to make the madvise() call only once
in case the size passed is >= thp_pagesize. I still get a regression. The regression
makes sense only if the brk VMA isn't a single VMA, but multiple - I checked the kernel
source, and there is a weird edge case (in the kernel version I ran SPEC on) wherein
if you set a memory policy using mbind() on a VMA, then you cannot extend that VMA.
So I cannot convince myself whether it is safe to assume that the heap VMA will always
be a single VMA.
> The patch applies on 259adb087dd9. Built on Aarch64, all malloc tests pass.
>
> 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);
> + }
> LIBC_PROBE (memory_sbrk_more, 2, brk, size);
> }
>
More information about the Libc-alpha
mailing list