malloc rarely benefits from Transparent Huge Page (THP)
Fengkai Sun
qcloud1014@gmail.com
Tue Jul 9 06:31:44 GMT 2024
Hello Adhemerval,
Thank you very much for your reply! I did not know about glibc's and
malloc's tunables before reading your reply.
I read that `glibc.malloc.hugetlb` was introduced in glibc 2.35. Does that
mean prior to 2.35, malloc just doesn't back small allocations to huge
pages?
By the way, I write a simple script to check how different versions of
malloc works with huge pages:
```
# generator.py
import sys
bit = sys.argv[1]
s = f"""#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[65536];
int main()
{{
for (int i = 0; i < ALLOC_1G / (1 << {bit}); i++) {{
void *p = malloc(1 << {bit});
memset(p, 0, 1 << {bit});
}}
FILE *f = fopen("/proc/self/smaps_rollup", "r");
size_t s = fread(buf, 1, 65536, f);
for (unsigned i = 0; i < s; i++)
printf("%c", buf[i]);
return 0;
}}
"""
out_path = "./main_interleave.c"
with open(out_path, "w") as f:
f.write(s)
```
```
#!/bin/bash
# run.sh
for (( i = 4;i<30;i++ ));
do
python generator.py $i
# using glibc 2.39
gcc -o main_interleave main_interleave.c
-Wl,--dynamic-linker,/home/iom/glibc-iom/build/glibc-O2/lib/ld-linux-x86-64.so.2
output=$(GLIBC_TUNABLES=glibc.malloc.hugetlb=1
LD_LIBRARY_PATH=/home/iom/glibc-iom/build/glibc-O2/lib ./main_interleave)
echo "Allocation: 1 << $i, total: 1 << 30"
echo "$output" | sed -E '/^Private_Dirty:/!d'
echo "$output" | sed -E '/^AnonHugePages:/!d'
echo "------"
done
```
`run.sh` gives the following output:
Using glibc 2.31, THP `always` mode: before allocation size reaches 1 <<
21, `AnonHugePages` is 0, then:
Allocation: 1 << 21, total: 1 << 30
Private_Dirty: 1050720 kB
AnonHugePages: 4096 kB
------
Allocation: 1 << 22, total: 1 << 30
Private_Dirty: 1049692 kB
AnonHugePages: 524288 kB
------
Allocation: 1 << 23, total: 1 << 30
Private_Dirty: 1049184 kB
AnonHugePages: 790528 kB
------
Allocation: 1 << 24, total: 1 << 30
Private_Dirty: 1048924 kB
AnonHugePages: 921600 kB
------
Allocation: 1 << 25, total: 1 << 30
Private_Dirty: 1048796 kB
AnonHugePages: 983040 kB
------
Allocation: 1 << 26, total: 1 << 30
Private_Dirty: 1048732 kB
AnonHugePages: 1015808 kB
------
Allocation: 1 << 27, total: 1 << 30
Private_Dirty: 1048704 kB
AnonHugePages: 1032192 kB
------
Allocation: 1 << 28, total: 1 << 30
Private_Dirty: 1048688 kB
AnonHugePages: 1040384 kB
------
Allocation: 1 << 29, total: 1 << 30
Private_Dirty: 1048680 kB
AnonHugePages: 1044480 kB
Using glibc 2.31, THP `madvise` mode: `AnonHugePages` always 0.
Using glibc 2.39, THP `always` mode, glibc.malloc.hugetlb=1: the same as
glibc 2.31, THP `always` mode.
Using glibc 2.39, THP `madvise` mode, glibc.malloc.hugetlb=1:
Allocation: 1 << 4, total: 1 << 30
Private_Dirty: 2098688 kB
AnonHugePages: 2095104 kB
------
Allocation: 1 << 5, total: 1 << 30
Private_Dirty: 1574144 kB
AnonHugePages: 1570816 kB
------
Allocation: 1 << 6, total: 1 << 30
Private_Dirty: 1312664 kB
AnonHugePages: 1308672 kB
------
Allocation: 1 << 7, total: 1 << 30
Private_Dirty: 1180460 kB
AnonHugePages: 1177600 kB
------
Allocation: 1 << 8, total: 1 << 30
Private_Dirty: 1116104 kB
AnonHugePages: 1112064 kB
------
Allocation: 1 << 9, total: 1 << 30
Private_Dirty: 1082276 kB
AnonHugePages: 1079296 kB
------
Allocation: 1 << 10, total: 1 << 30
Private_Dirty: 1066292 kB
AnonHugePages: 1062912 kB
------
Allocation: 1 << 11, total: 1 << 30
Private_Dirty: 1058388 kB
AnonHugePages: 1054720 kB
------
Allocation: 1 << 12, total: 1 << 30
Private_Dirty: 1052956 kB
AnonHugePages: 1050624 kB
------
Allocation: 1 << 13, total: 1 << 30
Private_Dirty: 1051116 kB
AnonHugePages: 1048576 kB
------
Allocation: 1 << 14, total: 1 << 30
Private_Dirty: 1051528 kB
AnonHugePages: 1048576 kB
------
Allocation: 1 << 15, total: 1 << 30
Private_Dirty: 1050148 kB
AnonHugePages: 1046528 kB
------
Allocation: 1 << 16, total: 1 << 30
Private_Dirty: 1049256 kB
AnonHugePages: 1046528 kB
------
Allocation: 1 << 17, total: 1 << 30
Private_Dirty: 1081392 kB
AnonHugePages: 0 kB
------
Allocation: 1 << 18, total: 1 << 30
Private_Dirty: 1065040 kB
AnonHugePages: 0 kB
------
Allocation: 1 << 19, total: 1 << 30
Private_Dirty: 1056868 kB
AnonHugePages: 0 kB
------
Allocation: 1 << 20, total: 1 << 30
Private_Dirty: 1052776 kB
AnonHugePages: 0 kB
------
Allocation: 1 << 21, total: 1 << 30
Private_Dirty: 1050736 kB
AnonHugePages: 4096 kB
------
Allocation: 1 << 22, total: 1 << 30
Private_Dirty: 1049712 kB
AnonHugePages: 528384 kB
------
Allocation: 1 << 23, total: 1 << 30
Private_Dirty: 1049200 kB
AnonHugePages: 786432 kB
------
Allocation: 1 << 24, total: 1 << 30
Private_Dirty: 1048940 kB
AnonHugePages: 917504 kB
------
Allocation: 1 << 25, total: 1 << 30
Private_Dirty: 1048816 kB
AnonHugePages: 983040 kB
------
Allocation: 1 << 26, total: 1 << 30
Private_Dirty: 1048748 kB
AnonHugePages: 1015808 kB
------
Allocation: 1 << 27, total: 1 << 30
Private_Dirty: 1048716 kB
AnonHugePages: 1032192 kB
------
Allocation: 1 << 28, total: 1 << 30
Private_Dirty: 1048700 kB
AnonHugePages: 1040384 kB
------
Allocation: 1 << 29, total: 1 << 30
Private_Dirty: 1048696 kB
AnonHugePages: 1044480 kB
I'm wondering why the memory backed to huge pages suddenly drops to 0 when
single allocation is 1 << 17 bytes.
Also, I found that even I use glibc 2.39, THP madvise mode, and
glibc.malloc.hugetlb=1, complex applications still does not benefit much
from THP (10M backed to THP even if it consumes ~500M VM). Do you know the
possible reason for this problem or some other tunables can help?
---
Thanks,
Fengkai
On Mon, Jul 8, 2024 at 5:02 AM Adhemerval Zanella Netto <
adhemerval.zanella@linaro.org> wrote:
>
>
> On 07/07/24 06:26, Fengkai Sun via Libc-help wrote:
> > Hi list,
> >
> > I'm expecting to see some speed up or at least reduction of dTLB misses
> of
> > a malloc-intensive program, after enabling THP on my system. However,
> > things do not quite go as expected.
> >
> > Consider the program below:
> >
> > ```
> > #include <stdio.h>
> > #include <stdlib.h>
> > #include <string.h>
> >
> > #define SEGMENT_1K (1 << 10)
> > #define SEGMENT_4K (1 << 12)
> > #define SEGMENT_8K (1 << 13)
> > #define SEGMENT_1M (1 << 20)
> > #define SEGMENT_2M (1 << 21)
> > #define SEGMENT_4M (1 << 22)
> > #define ALLOC_32M (1 << 25)
> > #define ALLOC_1G (1 << 30)
> >
> > int main()
> > {
> > for (int i = 0; i < ALLOC_1G / SEGMENT_1K; i++) {
> > void *p = malloc(SEGMENT_1K);
> > memset(p, 0, SEGMENT_1K);
> > }
> >
> > // used to check memory mappings from another shell
> > int sleeper;
> > scanf("%d", &sleeper);
> >
> > return 0;
> > }
> > ```
> >
> > `AnonHugePages` in `/proc/pid/smaps_rollup` indicates how much memory is
> > backed by THP in this process.
> >
> > Results:
> > Using SEGMENT_1K, 4K, 8K, 1M has AnonHugePages == 0;
> > Using SEGMENT_2M has AnonHugePages == 4096 kB;
> > Using SEGMENT_4M has AnonHugePages == 524288 kB.
> >
> > I believe this means malloc can only benefit from THP if a single
> > allocation is at least over 2M.
> > However, I don't think there would be much realistic workload doing that.
> > I've run a few quite complex applications at hand and found very few
> pages
> > backed to THP.
> >
> > I think it's a little counter-intuitive that the kernel (and/or malloc)
> > does not back small allocations to THP. Please tell me if I missed
> > something or this is an expected behavior.
> >
> >
> > I'm using glibc 2.31 under Ubuntu 20.04.1, kernel 5.15.0-113-generic.
> > Both `/sys/kernel/mm/transparent_hugepage/enabled` and
> > `/sys/kernel/mm/transparent_hugepage/defrag` are set to `always`.
>
> Did you try to use GLIBC_TUNABLES=glibc.malloc.hugetlb=1? I don't recall
> exactly if it would improve THP usage on 'always' mode, but it instruct
> malloc aligned brk allocation to the THP size (madvise_thp) and issue a
> madvise with (MADV_HUGEPAGE) to help kernel realize the allocation should
> be backed by HugePages. It also help on arena allocation by aligning
> the initial mmap.
>
>
More information about the Libc-help
mailing list