Ruby + jemalloc でメモリ使用量が増える場合

Ruby でスレッドを1000個ほど作るとプロセスサイズが 4GB ほどになります。

% ruby -e 'system "ps -o vsz -p #$$"; 1000.times{Thread.new{sleep}}; system "ps -o vsz -p #$$"'
   VSZ
 46392
   VSZ
4151572

jemalloc 使うと 2.5GB くらいになります。jemalloc すばらしい。

% LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 ruby -e 'system "ps -o vsz -p #$$"; 1000.times{Thread.new{sleep}}; system "ps -o vsz -p #$$"'
   VSZ
 55672
   VSZ
2509080

ところが jemalloc を使ったほうが時間がかかります。

% time ruby -e '1000.times{Thread.new{sleep}}'
ruby -e '1000.times{Thread.new{sleep}}'  0.10s user 0.06s system 115% cpu 0.132 total
% time LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 ruby -e '1000.times{Thread.new{sleep}}'
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 ruby -e   0.23s user 0.81s system 109% cpu 0.941 total

jemalloc を使わない場合は 0.13秒ですが、jemalloc を使うと 0.94秒かかってます。

プロセスサイズではなく OS の使用メモリを見てみます。

% ruby -e 'system "free"; 1000.times{Thread.new{sleep}}; system "free"'
             total       used       free     shared    buffers     cached
Mem:       8056184    7719868     336316      97356     587492    5373632
-/+ buffers/cache:    1758744    6297440
Swap:      4194300       2800    4191500
             total       used       free     shared    buffers     cached
Mem:       8056184    7766988     289196      97356     587492    5373632
-/+ buffers/cache:    1805864    6250320
Swap:      4194300       2800    4191500

jemalloc を使わない場合は、プロセスサイズは 4GB くらいでしたが、実際に使われているメモリは 47MB くらいです (前後 の used の差分)。

% LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 ruby -e 'system "free"; 1000.times{Thread.new{sleep}}; system "free"'
             total       used       free     shared    buffers     cached
Mem:       8056184    7721732     334452      89052     587520    5365352
-/+ buffers/cache:    1768860    6287324
Swap:      4194300       2800    4191500
             total       used       free     shared    buffers     cached
Mem:       8056184    7780264     275920      87260     587520    5363568
-/+ buffers/cache:    1829176    6227008
Swap:      4194300       2800    4191500

jemalloc を使った場合は 60MB くらいで、逆に多く使用してしまっています。

さらに 10個ほど fork してみます。

% ruby -e 'system "free"; 1000.times{Thread.new{sleep}}; 10.times{fork{sleep}}; sleep 1; system "free"'
             total       used       free     shared    buffers     cached
Mem:       8056184    2053828    6002356      68744     372796     613836
-/+ buffers/cache:    1067196    6988988
Swap:      4194300          0    4194300
             total       used       free     shared    buffers     cached
Mem:       8056184    2246508    5809676      68744     372804     613856
-/+ buffers/cache:    1259848    6796336
Swap:      4194300          0    4194300

メモリ使用量の増加分は 192MB 程度です。

% LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 ruby -e 'system "free"; 1000.times{Thread.new{sleep}}; 10.times{fork{sleep}}; sleep 1; system "free"'
             total       used       free     shared    buffers     cached
Mem:       8056184    2084772    5971412      67856     372892     613568
-/+ buffers/cache:    1098312    6957872
Swap:      4194300          0    4194300
             total       used       free     shared    buffers     cached
Mem:       8056184    4041740    4014444      67856     372892     613568
-/+ buffers/cache:    3055280    5000904
Swap:      4194300          0    4194300

jemalloc 使うと 2GB 程メモリを食ってしまっています。

なんでこういうことになるのか理屈は全然わかってませんが、jemalloc 使うことでメモリに悪影響を及ぼすこともあるという例でした。

まあ fork なんて使うのが悪いのかもしれませんけどね。