OOM Killer 対策として消費メモリを削減する方法を探していたら、驚きの情報を見つけましたので、C言語で検証してみました。
https://man7.org/linux/man-pages/man3/mallopt.3.html で説明されているように、
glibc には dynamic mmap threshold という仕組みが実装されています。この仕組みが有効になっている場合、メモリの割り当て/解放を繰り返すプログラムを実行した際に、一部のメモリがOSに返却されなくなるという事象が発生します。これは、長期間動作を継続するプログラムにとって、無駄なメモリ消費の原因となることがあります。
実行結果例1:
物理メモリを6GB以上搭載した RHEL8 以降の環境で、以下のように約4GBのメモリの割り当て/解放を繰り返すプログラムを実行した場合、OSから割り当てたメモリの約8割がOSに返却されないまま(再割り当て要求に備えて glibc が保持したまま)の状態が発生することが観測できます。
--------------------------------------------------------------------------------
[root@localhost ~]# uname -r
4.18.0-80.el8.x86_64
[root@localhost ~]# ls -l /lib64/libc.so.6
lrwxrwxrwx 1 root root 12 May 14 2019 /lib64/libc.so.6 -> libc-2.28.so
[root@localhost ~]# gcc -Wall -O2 -x c - -o rss << EOF
> #include <stdio.h>
> #include <string.h>
> #include <stdlib.h>
> #include <malloc.h>
>
> static void show_rss(void)
> {
> static FILE *fp = NULL;
> static char buffer[4096];
> char *cp;
> unsigned long rss;
> if (!fp) {
> fp = fopen("/proc/self/status", "r");
> if (!fp)
> return;
> }
> rewind(fp);
> buffer[fread(buffer, 1, sizeof(buffer) - 1, fp)] = 0;
> cp = strstr(buffer, "VmRSS:");
> if (cp && sscanf(cp + 6, "%lu", &rss) == 1)
> printf("\tRSS: %lu KB\n", rss);
> }
>
> int main(int argc, char *argv[])
> {
> static char *buf[256];
> int i, j, k;
> srand(0);
> for (k = 0; k < 20; k++) {
> unsigned long sum = 0;
> for (i = 0; i < 256; i++) {
> unsigned int size = (random() % 8192) * 4096;
> char *cp = malloc(size);
> if (!cp)
> return 1;
> for (j = 0; j < size; j += 4096)
> cp[j] = 1;
> sum += size;
> buf[i] = cp;
> }
> printf("%d\t+%lu KB\n", k, sum / 1024);
> show_rss();
> for (i = 0; i < 256; i++)
> free(buf[i]);
> printf("\t-%lu KB\n", sum / 1024);
> if (argc > 1)
> malloc_trim(0);
> show_rss();
> }
> return 0;
> }
> EOF
[root@localhost ~]# ./rss
0 +4122392 KB
RSS: 4123292 KB
-4122392 KB
RSS: 1380 KB
1 +4182160 KB
RSS: 4183300 KB
-4182160 KB
RSS: 1508 KB
2 +4130460 KB
RSS: 4131836 KB
-4130460 KB
RSS: 1508 KB
3 +4241984 KB
RSS: 4243208 KB
-4241984 KB
RSS: 1508 KB
4 +4198020 KB
RSS: 4199160 KB
-4198020 KB
RSS: 1508 KB
5 +4392688 KB
RSS: 4393868 KB
-4392688 KB
RSS: 1508 KB
6 +4246196 KB
RSS: 4247432 KB
-4246196 KB
RSS: 3407164 KB
7 +4039140 KB
RSS: 4040608 KB
-4039140 KB
RSS: 3407164 KB
8 +4346760 KB
RSS: 4348884 KB
-4346760 KB
RSS: 3407164 KB
9 +4369996 KB
RSS: 4371692 KB
-4369996 KB
RSS: 3407164 KB
10 +3937740 KB
RSS: 3939848 KB
-3937740 KB
RSS: 3407164 KB
11 +4044560 KB
RSS: 4046120 KB
-4044560 KB
RSS: 3407164 KB
12 +4193628 KB
RSS: 4196104 KB
-4193628 KB
RSS: 3407164 KB
13 +4061028 KB
RSS: 4062484 KB
-4061028 KB
RSS: 3407164 KB
14 +4529360 KB
RSS: 4531736 KB
-4529360 KB
RSS: 3407164 KB
15 +4101504 KB
RSS: 4102856 KB
-4101504 KB
RSS: 3407164 KB
16 +4254812 KB
RSS: 4257280 KB
-4254812 KB
RSS: 3407164 KB
17 +4335764 KB
RSS: 4339976 KB
-4335764 KB
RSS: 3407164 KB
18 +4118404 KB
RSS: 4119636 KB
-4118404 KB
RSS: 3407164 KB
19 +4049376 KB
RSS: 4050776 KB
-4049376 KB
RSS: 3407164 KB
--------------------------------------------------------------------------------
実行結果例2:
https://man7.org/linux/man-pages/man3/malloc_trim.3.html で説明されている malloc_trim() という関数を呼び出すことでOSに返却可能なメモリを返却することが可能になります。しかし、ソースコードに対する改修が必要となるため簡単に採用できるとは限りません。
--------------------------------------------------------------------------------
[root@localhost ~]# ./rss 1
0 +4122392 KB
RSS: 4123356 KB
-4122392 KB
RSS: 1376 KB
1 +4182160 KB
RSS: 4183280 KB
-4182160 KB
RSS: 1376 KB
2 +4130460 KB
RSS: 4131756 KB
-4130460 KB
RSS: 1376 KB
3 +4241984 KB
RSS: 4243340 KB
-4241984 KB
RSS: 1376 KB
4 +4198020 KB
RSS: 4199232 KB
-4198020 KB
RSS: 1376 KB
5 +4392688 KB
RSS: 4394072 KB
-4392688 KB
RSS: 1376 KB
6 +4246196 KB
RSS: 4247444 KB
-4246196 KB
RSS: 1380 KB
7 +4039140 KB
RSS: 4040316 KB
-4039140 KB
RSS: 1380 KB
8 +4346760 KB
RSS: 4348104 KB
-4346760 KB
RSS: 1380 KB
9 +4369996 KB
RSS: 4371196 KB
-4369996 KB
RSS: 1380 KB
10 +3937740 KB
RSS: 3939036 KB
-3937740 KB
RSS: 1380 KB
11 +4044560 KB
RSS: 4045792 KB
-4044560 KB
RSS: 1380 KB
12 +4193628 KB
RSS: 4194892 KB
-4193628 KB
RSS: 1380 KB
13 +4061028 KB
RSS: 4062356 KB
-4061028 KB
RSS: 1380 KB
14 +4529360 KB
RSS: 4530592 KB
-4529360 KB
RSS: 1380 KB
15 +4101504 KB
RSS: 4102700 KB
-4101504 KB
RSS: 1380 KB
16 +4254812 KB
RSS: 4256008 KB
-4254812 KB
RSS: 1380 KB
17 +4335764 KB
RSS: 4337904 KB
-4335764 KB
RSS: 1380 KB
18 +4118404 KB
RSS: 4119732 KB
-4118404 KB
RSS: 1380 KB
19 +4049376 KB
RSS: 4050680 KB
-4049376 KB
RSS: 1380 KB
--------------------------------------------------------------------------------
実行結果例3:
malloc_trim() を呼び出すことができない場合には、 dynamic mmap threshold を無効化するための環境変数( MALLOC_TRIM_THRESHOLD_ MALLOC_TOP_PAD_ MALLOC_MMAP_THRESHOLD_ MALLOC_MMAP_MAX_ の内の1個以上)を設定することで緩和する(OSに返却されないままとなる量を削減する)ことができます。
--------------------------------------------------------------------------------
[root@localhost ~]# MALLOC_TRIM_THRESHOLD_=131072 MALLOC_MMAP_THRESHOLD_=131072 ./rss
0 +4122392 KB
RSS: 4123480 KB
-4122392 KB
RSS: 1476 KB
1 +4182160 KB
RSS: 4183540 KB
-4182160 KB
RSS: 1476 KB
2 +4130460 KB
RSS: 4131716 KB
-4130460 KB
RSS: 1476 KB
3 +4241984 KB
RSS: 4243208 KB
-4241984 KB
RSS: 1564 KB
4 +4198020 KB
RSS: 4199580 KB
-4198020 KB
RSS: 1564 KB
5 +4392688 KB
RSS: 4394228 KB
-4392688 KB
RSS: 1564 KB
6 +4246196 KB
RSS: 4247716 KB
-4246196 KB
RSS: 1564 KB
7 +4039140 KB
RSS: 4040616 KB
-4039140 KB
RSS: 1564 KB
8 +4346760 KB
RSS: 4348128 KB
-4346760 KB
RSS: 1564 KB
9 +4369996 KB
RSS: 4371344 KB
-4369996 KB
RSS: 1564 KB
10 +3937740 KB
RSS: 3939268 KB
-3937740 KB
RSS: 1564 KB
11 +4044560 KB
RSS: 4045940 KB
-4044560 KB
RSS: 1584 KB
12 +4193628 KB
RSS: 4195188 KB
-4193628 KB
RSS: 1584 KB
13 +4061028 KB
RSS: 4062456 KB
-4061028 KB
RSS: 1584 KB
14 +4529360 KB
RSS: 4530728 KB
-4529360 KB
RSS: 1584 KB
15 +4101504 KB
RSS: 4102740 KB
-4101504 KB
RSS: 1604 KB
16 +4254812 KB
RSS: 4256256 KB
-4254812 KB
RSS: 1604 KB
17 +4335764 KB
RSS: 4337368 KB
-4335764 KB
RSS: 1604 KB
18 +4118404 KB
RSS: 4119776 KB
-4118404 KB
RSS: 1604 KB
19 +4049376 KB
RSS: 4050732 KB
-4049376 KB
RSS: 1604 KB
--------------------------------------------------------------------------------
実行結果例4:
この挙動は RHEL7 以前の環境でもある程度再現するようです。以下のように約96MBのメモリの割り当て/解放を繰り返すプログラムを実行した場合、約63MBがOSに返却されないままの状態が発生することが観測できます。GBレベルでの差異ではないので見逃してしまうかもしれませんが、 malloc_trim() を呼び出すように修正したり環境変数を指定することにより緩和するという対処は有効なようです。
--------------------------------------------------------------------------------
[root@localhost ~]# uname -r
2.6.32-71.el6.x86_64
[root@localhost ~]# ls -l /lib64/libc.so.6
lrwxrwxrwx. 1 root root 12 Aug 18 19:13 /lib64/libc.so.6 -> libc-2.12.so
[root@localhost ~]# gcc -Wall -O2 -x c - -o rss << EOF
> #include <stdio.h>
> #include <string.h>
> #include <stdlib.h>
> #include <malloc.h>
>
> static void show_rss(void)
> {
> static FILE *fp = NULL;
> static char buffer[4096];
> char *cp;
> unsigned long rss;
> if (!fp) {
> fp = fopen("/proc/self/status", "r");
> if (!fp)
> return;
> }
> rewind(fp);
> buffer[fread(buffer, 1, sizeof(buffer) - 1, fp)] = 0;
> cp = strstr(buffer, "VmRSS:");
> if (cp && sscanf(cp + 6, "%lu", &rss) == 1)
> printf("\tRSS: %lu KB\n", rss);
> }
>
> int main(int argc, char *argv[])
> {
> static char *buf[3];
> int i, j, k;
> srand(0);
> for (k = 28; k <= 32; k++) {
> unsigned long sum = 0;
> for (i = 0; i < 3; i++) {
> unsigned int size = (k + i) * 1048576 - (random() % 16) * 4096;
> char *cp = malloc(size);
> if (!cp)
> return 1;
> for (j = 0; j < size; j += 4096)
> cp[j] = 1;
> sum += size;
> buf[i] = cp;
> }
> printf("%d\t+%lu KB\n", k, sum / 1024);
> show_rss();
> for (i = 0; i < 3; i++)
> free(buf[i]);
> printf("\t-%lu KB\n", sum / 1024);
> if (argc > 1)
> malloc_trim(0);
> show_rss();
> }
> return 0;
> }
> EOF
[root@localhost ~]# ./rss
28 +89000 KB
RSS: 89480 KB
-89000 KB
RSS: 520 KB
29 +92084 KB
RSS: 92604 KB
-92084 KB
RSS: 30208 KB
30 +95108 KB
RSS: 95632 KB
-95108 KB
RSS: 31204 KB
31 +98168 KB
RSS: 98692 KB
-98168 KB
RSS: 64944 KB
32 +101312 KB
RSS: 133496 KB
-101312 KB
RSS: 64944 KB
[root@localhost ~]# ./rss 1
28 +89000 KB
RSS: 89476 KB
-89000 KB
RSS: 520 KB
29 +92084 KB
RSS: 92604 KB
-92084 KB
RSS: 524 KB
30 +95108 KB
RSS: 95632 KB
-95108 KB
RSS: 524 KB
31 +98168 KB
RSS: 98692 KB
-98168 KB
RSS: 524 KB
32 +101312 KB
RSS: 101836 KB
-101312 KB
RSS: 524 KB
[root@localhost ~]# MALLOC_TRIM_THRESHOLD_=131072 MALLOC_MMAP_THRESHOLD_=131072 ./rss
28 +89000 KB
RSS: 89500 KB
-89000 KB
RSS: 528 KB
29 +92084 KB
RSS: 92612 KB
-92084 KB
RSS: 528 KB
30 +95108 KB
RSS: 95636 KB
-95108 KB
RSS: 528 KB
31 +98168 KB
RSS: 98696 KB
-98168 KB
RSS: 528 KB
32 +101312 KB
RSS: 101840 KB
-101312 KB
RSS: 528 KB
--------------------------------------------------------------------------------