MySQLがOSにメモリを返さないというような話を前に書いた。それに付随してそういえばGoで動いているサーバもメモリ使用率高かったなというのを思い出したのでGoのメモリ管理についても調べてみた。
とてもわかりやすくまとまっている記事だった。Goのメモリアロケータは独自実装であるとのことでtcmallocがベースになっているらしい。
実メモリ使用量が減少しない
Go 1.12以上 + Linux 4.5以上でだとmadviseのシステムコールについてデフォルトでMADV_DONTNEED ではなくMADV_FREEを使用するようになっているらしいのです。MADV_FREEを指定した場合はOSはメモリが欲しくなるまでその実メモリの割り当てを開放しないというフラグになります。メモリの割り当て処理は重いのでGo的には解放を指示はするが再度必要になった際にはメモリ割り当て処理を高速化することが可能になります。ちなみに環境変数に GODEBUG=madvdontneed=1 を設定することでMADV_DONTNEEDを使うようになります。
ちなみにGo 1.16からはデフォルトでMADV_DONTNEEDになるらしいです。割りに合わないというのが読み取れましたw。RSS のようなプロセスレベルのメモリ統計が知りたいケースの方が多いんだろうなぁと感じました。
https://go-review.googlesource.com/c/go/+/267100/
microsoft/mimalloc は確かデフォルトでMADV_FREEを使ってた
メモリアロケータの評価をしてたのがだいぶ前にあってこのときにmicrosoft/mimallocも試してたのですが死ぬほどRSSが高くなったなぁとなって調べたらMADV_FREEが指定されていたよねというのがあったのを思い出しました。(Swift や Python のような参照カウントを利用している言語向けにチューニングされているみたいな特徴があったやつ)
(おまけ) 仮想メモリが高い
Why does my Go process use so much virtual memory? The Go memory allocator reserves a large region of virtual memory as an arena for allocations. This virtual memory is local to the specific Go process; the reservation does not deprive other processes of memory. To find the amount of actual memory allocated to a Go process, use the Unix top command and consult the RES (Linux) or RSIZE (macOS) columns.
と、公式書いてあった。オーバーコミットさせない運用してるケースだと辛いよねという。(swap足すなり設定値チューニングするなりはできるが...)