jemallocとかLD_PRELOADについて調べてみた

何周遅れか分かりませんが調べてみました。僕の理解は浅いので間違っている可能性大ですが自分用にメモしておきます。

malloc とは?

C 言語ではmallocという関数を使って、使いたいメモリを実行中に割り当てることができます。例えば Wikipedia によればこんな感じ(適当に main 足してます)。

#include <stdlib.h>
int main() {
    /* 10個のintの配列のためのメモリを確保 */
    int *ptr = malloc(sizeof (int) * 10);
    if (ptr == NULL)
        exit(EXIT_FAILURE); /* メモリを確保できなかったので、exit */

    /* 確保成功 */
}

これを素朴に gcc でコンパイルすると、glibc(libc.so.6)が動的にリンクされます(ここら辺の仕組みまだよく理解してないです)。以下は CentOS5 の 64bit 版での例。

$ gcc hoge.c
$ ldd a.out
libc.so.6 => /lib64/libc.so.6 (0x00000037be800000)
/lib64/ld-linux-x86-64.so.2 (0x00000037be400000)

で、a.outの中で実行されるmalloc関数の実態は動的にリンクされている glibc の中のmallocが実行されます。

$ nm a.out | grep malloc
U malloc@@GLIBC_2.2.5
$ nm -D /lib64/libc.so.6 | grep malloc
...
00000037be874c70 T malloc

glibc の malloc の実装自体の入門については kosaki さんのこちらのスライドがものすごい勉強になりますのでぜひ御覧ください。

jemalloc とは?

glibc の malloc は特にマルチスレッドな環境下での性能がよろしくないそうで、そこを解決した malloc の別の実装として jemalloc というのがあることを昨日知りました。知るのが遅すぎる。。。

ちなみに存在を知ったのは tagomorisu さんと frsyuki さんが fluentd のメモリリーク疑惑についてやり取りしてる中で、glibc の malloc によってメモリリークの様にプロセスサイズが膨れ上がるということがあるということをつぶやかれていたためです。

jemalloc 関連で特にインパクトあるのが、facebook がパフォーマンス改善のために jemalloc をさらにチューニングしてみたという記事と、mysql でも同じく効果があったという話です。

なんということでしょう。僕の浅はかな頭の中では、malloc なんて改善の余地がないんだろうとか思ってました。。。malloc の実装を変えるだけでこんなにも変わるものなのか。。。まぁ実際自分で実験してみたわけではないので鵜呑みにするわけではないですが、標準ライブラリを入れ替えるだけで性能上がるってのはヨダレの出るお話ですね。

LD_PRELOAD で簡単に入れ替え

そこではて?と思いました。そんな簡単に glibc malloc から jemalloc に入れ替えられるものなのか?無い知識振り絞りつつ調べていくと、LD_PRELOAD という環境変数がかなりイケメンであることが分かりました。そのイケメン具合についてはこちらの記事がものすごい分かりやすいです。

一番最初に説明したような、普通にgccでコンパイルされた実行ファイルでは大抵最後の方にある glibc の共有ライブラリにしか malloc シンボル(関数名っぽいもの?)がないので、それが使われます。

この共有ライブラリの検索の一番先頭に追加する手段として、LD_PRELOAD という環境変数が使えます。細かい話は先のリンク先が詳しいです。写経して試せるレベルなのでぜひ試して見てください。ちょー簡単に効果を見るならこんな感じ(epel から jemalloc をインストール後)。数が増えてるのは libjemalloc.so.1 自体がさらに動的リンクしてるからですね。

$ LD_PRELOAD=/usr/lib64/libjemalloc.so.1 ldd a.out
/usr/lib64/libjemalloc.so.1 (0x00002b55b088c000)
libc.so.6 => /lib64/libc.so.6 (0x00000037be800000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00000037bf400000)
libdl.so.2 => /lib64/libdl.so.2 (0x00000037bf000000)
/lib64/ld-linux-x86-64.so.2 (0x00000037be400000)

というわけで、あなたが使っているプログラムの実行時に試しに LD_PRELOAD で jemalloc を指定して実行するだけで、あら不思議!malloc の実装が jemalloc に置き換わってしまうのです!!!先の Facebook の Mark の記事の中でも LD_PRELOAD で入れ替えてみた、みたいな記述がありますね。なんて簡単!

おわりに

自分の知識不足を痛感しました。。。単純なメモリリーク以外に malloc のフラグメンテーションによってヒープが膨れてしまう場合があること、malloc の実装がいくつもあること、それが簡単に入れ替え可能なこと、などなど、何一つ分かってなかったですね。

この辺の知識深めるのに「Binary Hacks」や「Debug Hacks」がいいらしいので、いい加減この 2 冊も入手して読んでしまいたいところ。。。そしてこうして勉強したいことばかり増えていって何一つアウトプットにいそしむ時間が取れない。。。

あと、LD_PRELOAD の記事は最初 MacOSX で試してみようと思ってたんですが、本題とは関係ないltraceを MacOSX でどうやるんだろうってのを調べるのに夢中になってしまいdtrace関連を調べまくったあげく、どうやら MacOSX のdtraceがバグってるとかでdtrace -cで実行コマンド渡して解析するのが動かないっぽくて諦めるという謎の作業をしてました orz

  • mizzy 12-04-03 (火) 11:44

    > gcc でコンパイルすると、glibc(libc.so.6)が動的にリンクされます(ここら辺の仕組みまだよく理解してないです)

    この辺りについては「GNU 開発ツール」という本がとてもわかりやすいですよ。

    http://www.oversea-pub.com/publications.htm

  • riywo 12-04-03 (火) 23:46

    mizzy さん、ありがとうございます!!!
    まさに知りたい内容の本なので入手したいと思います!!!