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さん、ありがとうございます!!!
まさに知りたい内容の本なので入手したいと思います!!!