💨
glibc mallocメモリ断片化の解決例
20MBのメモリを一時的に確保するとして、一回目はすぐOSに返され、RSSが下がるのに、二回目から下がらず、断片化してもりもりRSSが上がっていくことがある。
これはglibc mallocが一回目mmapで確保するのに、その後brkで確保し始めるからだった。
glibc mallocのmmapとbrkの閾値は128 * 1024 bytesから始まるが、標準で動的です。
glibc/malloc/malloc.cより
# if __WORDSIZE == 32
# define DEFAULT_MMAP_THRESHOLD_MAX (512 * 1024)
# else
# define DEFAULT_MMAP_THRESHOLD_MAX (4 * 1024 * 1024 * sizeof(long))
# endif
#endif
64bit環境ではsizeof(long) == 8なので、閾値は32MBまで上がり得ます。
実際に確かめてみましょう。
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void malloc_and_free() {
const size_t size = 20 * 1024 * 1024;
puts("malloc");
void *p = malloc(size);
memset(p, 0, size);
sleep(5);
puts("free");
free(p);
sleep(5);
}
int main() {
malloc_and_free();
malloc_and_free();
malloc_and_free();
return 0;
}
$ gcc main.c
$ ./a.out
タイミングで
$ ps u -p $(pgrep a.out)
なんかをして計れば、2回めのfreeからはRSSが下がらないことが見えるでしょう。同期的で単純な場合これでも問題ないし、ほんの少し速くなってむしろ嬉しいかもしれませんが、非同期だったり、マルチスレッドだったり、アリーナがわかれて再利用されずただただ無駄なRSSが上がっていくのは嬉しくありません。mmapの閾値を設定して解決してみましょう。
$ MALLOC_MMAP_THRESHOLD_=131072 ./a.out
10~30MBぐらいのメモリが断片化してRSSが膨れるなら、環境変数を適当に設定して、回避できますね。
はじめからかなりメモリを食うjemallocやmimallocを使うより、こちらのほうがいい場合もあるかもしれません。
おわり
Discussion