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 --------------------------------------------------------------------------------