Part3 「jemallocがRubyアプリケーションのメモリ利用効率を向上させるのか...?」
3回に分けて記事を作成する。
本記事はそのPart3である。
Part1 「Rubyのメモリ肥大化はfragmentation(断片化)が原因(かもしれない)」
Part2 「Rubyのメモリ仕様 -ブラックボックスを覗く-」
Part3 「jemallocがRubyアプリケーションのメモリ利用効率を向上させるのか...?」
目次
- Sidekiqの開発者もjemallocを推奨している
- jemallocがRubyアプリケーションを効率的にする理由...
- jemalloc導入による改善レポート
- パフォーマンスも向上する!?
- jemallocをRubyのデフォルトへ
- より正しくて詳細な調査を...
- (付録)メモリに関するTips
Sidekiqの開発者もjemallocを推奨している
Rubyはデフォルトではglibc、つまりmallocを使用する。Ruby2.2からは、jemallocを有効化するオプションが追加された。jemallocに変更することで、メモリ使用量を減らすことに成功したという報告がある。Rubyアプリケーションにjemalloc を導入すること”のみ”で、メモリ使用の向上を図れる可能性があるならばレバレッジは大きい。
Sidekiqの開発者もjemallocを推奨している。
I wish ruby-core would pull in jemalloc as the default allocator but they seem content with glibc.(中略)Let’s give ruby-core all the data they need to make the right decision.
私はruby-coreがjemallocをデフォルトのアロケータにすることを望むが、彼らはglibcに満足している。ruby-coreが正しい決断を出来るようにデータを提供しよう。
「Taming Rails memory bloat」 ※訳は筆者による
jemallocがRubyアプリケーションを効率的にする理由...
mallocをjemallocに置き換えることで、メモリの利用効率が向上するのは何故か。誠に申し訳ないが、筆者はその要因を調べきることは出来なかった。1つヒントになる記事を見つけたので、取り上げておく。「mod_mrubyのメモリ問題をvalgrindで調査の上jemallocで改善」では、最小割当サイズがmallocより小さいことがjemallocのメリットとして挙げられている。malloc、jemallocはともにスケーラブルなメモリ割当を行っている。実はmallocが8byte刻みに対し、jemallocは16byte刻みと、jemallocの方が”幅”は大きい。ただし、最小割当サイズはmallocが16byte、jemallocは8byteである。これがfragmentation(断片化)を抑え、結果メモリ使用量が削減できるとの指摘がされている。
jemalloc導入による改善レポート
改めてjemallocについて整理しておこう。jemallocは、mallocに代わるメモリアロケータであり、mallocに起因するfragmentation(断片化)を解消する。また、マルチスレッドプログラムのスケーラビリティを改善している。
jemalloc is a general purpose malloc(3)implementation that emphasizes fragmentation avoidance and scalable concurrency support.
jemalloc README
jemallocの導入により、成果があったとする報告をまとめておこう。なおこれらは検索で簡単にヒットした中から、いくつか選んだものだ。
「Ruby × jemallocのすすめ」
平均して5%程度改善
「How we halved our memory consumption in Rails with jemalloc」
メモリ使用率が半分近くまで改善
「REDUCING SIDEKIQ MEMORY USAGE WITH JEMALLOC」
メモリ使用量が1/4まで減少
「How jemalloc improved memory usage of our Rails application」
メモリ使用率が10%程改善。Sidekiqも1/6まで抑制
パフォーマンスも向上する!?
先程のそれぞれのレポートでは、メモリの改善とともに、パフォーマンスも改善したという報告がされている。「Part1 Rubyのメモリ肥大化はfragmentation(断片化)が原因(かもしれない)」では、Rubyにおけるメモリとパフォーマンスはトレードオフにあると言及した。MALLOC_ARENA_MAX=2の設定やCompactionは、メモリの改善と引き換えに、パフォーマンスを悪化させる。しかしどうやら、jemallocではその両立が成立するようだ ..!
killing two birds with one stone...!
参考
Benchmarking Ruby's Heap: malloc, tcmalloc, jemalloc
malloc、jemalloc、tcmalloc(Google製メモリアロケータ)を比較したもの。mallocに対し、jemallocは11%程度、tcmallocは3~10%の向上している。
jemallocをRubyのデフォルトへ
jemallocがメモリの使用量を減らし、パフォーマンスを向上させるならば、Rubyのデフォルトにすべきという提起もされている。ruby-core(Use jemalloc by default?)にチケットがある。然乍、チケットのオープンは2018年であり、直近では更新されていない。なおRedisでは、ver2.4からjemallocに移行している。
より正しくて詳細な調査を...
3回に分けて Rubyのメモリ肥大化に関するテーマを扱ってきた。読者の中には、記事の分かりにくさに辟易された方もおられるだろう。いやそもそも読者は存在するのか...?分かりにくさの要因は、筆者自身の理解が不足しており、かつ全体の構成が定まっていないまま、ここまで書いていることにある。
最後に今度筆者自身が取り組むべきことを述べておく。まずは自分で検証を行い、データを集めたい。この記事のように2、3次情報を寄せ集めても大した価値はない。検証データをもとに、”構成を整えて”レポートを作成したい。
(付録)メモリに関するTips
メモリに関するTipsを挙げておく。ただし、運用するアプリケーションにメモリの課題があるならば、第一に疑うべきは不適切な実装をしていないかの調査だ。またここに挙げたTips以外でも、メモリ効率を向上させる手段は数多くある。
シンボルを使う
Rubyの内部実装では、メソッド名や変数名、定数名、クラス名などの`名前'を整数で管理しています。
(Ruby 3.1 リファレンスマニュアル Symbolクラス)
-262 〜262-1の範囲に収まる整数のメモリサイズは40バイトだ。つまり単一のSlot、RValueにぴったり収まる。
破壊的メソッドの使用
mapよりも、map!の方がメモリ効率の観点では優れている。破壊的メソッドを用いることで、オブジェクトの生成を抑え、メモリを節約する。
JsonのPerseにgemを用いる
yajl-ruby
READMEによれば、Json.perseと比較して、メモリ使用量を減らし、Parse、Encode時間を短縮することが出来る。
Lazy関数の使用
膨大な値をmapやselectなどで、繰り返し処理を行う場合は、Lazy関数を使用するとメモリの消費を抑制できる。
Discussion