世界を動かす技術を、日本語で。

Jemalloc ポストモーテム

概要

jemalloc は2004年に構想され、20年間にわたり公開利用されたメモリアロケータ。 開発フェーズ ごとに成功と課題を経験し、主にFreeBSD、Firefox、Facebook/Metaで進化。 オープンソース として公開は継続するが、上流開発は終了。 外部との連携やニーズ把握の難しさ、技術的負債が課題として浮上。 今後はフォークや新たな開発者による進展に期待。

jemallocの開発フェーズと回顧

  • Phase 0: Lyken

    • 2004年、科学計算向け言語Lykenの開発とともに 手動メモリアロケータ を実装
    • 2005年5月、機能的に完成
    • ガーベジコレクタは未完成
    • 2005年9月、 FreeBSD への統合を開始
    • Lykenからアロケータを削除し、システムアロケータの薄いラッパーに移行
    • Lykenで必要だった 統計機能 は後年jemallocに実装
  • Phase 1: FreeBSD

    • 2005年、マルチプロセッサ化の進展を背景に phkmalloc からの置換を目指す
    • 初期統合後、 断片化問題 が顕在化(特にKDEアプリで深刻)
    • 原因は サイズクラス分離 の欠如による統一的な割り当て方式
    • 研究と実験を経て、 サイズ分離型レイアウト に全面刷新(2006年BSDCan論文で解説)
  • Phase 1.5: Firefox

    • 2007年、 Mozilla Firefox 3 のリリース前、断片化問題が深刻化
    • Linux移植は容易だったが、 Windows対応 は困難
    • FreeBSD libcにあったjemallocを フォーク し、移植性向上
    • 実装が単一ファイルで管理しやすかったが、 複雑化 が進行
    • Firefox開発者が上流に貢献するも、 フォーク版の方が高性能 という結果に
    • 最適化の行き過ぎか、性能退化かは不明
  • Phase 2: Facebook

    • 2009年、Facebookでの 導入障壁は計測機能の不足
    • tcmallocやpprof によるヒーププロファイリングが必須
    • jemalloc 1.0.0で pprof互換ヒーププロファイリング を実装
    • GitHubへ移行し、外部貢献も増加
    • 3.0.0で テスト基盤とValgrind対応、4.xで データ削除の最適化とJSONテレメトリ、5.xで extents化巨大ページ対応
    • 5.0.0で Valgrind対応を削除、Facebook内で不要と判断
    • Rust開発者などから反発、 Rustバイナリからの除外 が加速
    • Facebookの 大規模テレメトリ が性能改善に大きく寄与
    • 継続的インテグレーションや包括的テレメトリも整備
    • 2017年以降、Qi Wangらが開発・保守を継続
    • Metaへの社名変更以降、 コア技術への投資減少、HPA(Huge Page Allocation)の停滞
  • Phase 4: Stasis(停滞期)

    • 上流開発の終了 を宣言
    • Metaと外部のニーズ乖離、Meta独自方針へ
    • 技術的負債の返済には膨大なリファクタリングが必要
    • devブランチや5.3.0リリースからの フォーク に期待
    • Valgrind対応削除 など、外部利用者への認識不足が問題に
    • Androidでの利用や置換にも当初気付かず
    • 他組織からの 主要貢献者確保に失敗
    • CMake対応なども未完
    • オープンな開発体制 だけでは独立プロジェクトとしての発展は困難

jemallocの総括と今後

  • ガーベジコレクション 推進派でありながら、手動メモリアロケータ開発に大きな満足感
  • 協力者・利用者・支援者 への感謝
  • 今後は 新たな開発者やフォーク による発展に期待
  • オープンソースとしての公開 は今後も継続

開発フェーズ別の成功・失敗まとめ

  • 成功例

    • FreeBSDやFacebookなど大規模基盤での採用
    • 高性能・低断片化・豊富なテレメトリ
    • 外部貢献による機能拡張
  • 失敗例

    • 断片化問題やValgrind対応削除による反発
    • 外部利用者や他プロジェクトのニーズ把握不足
    • 他組織との主要貢献者ネットワーク形成に失敗
    • 技術的負債の蓄積と停滞

今後の展望

  • 既存の5.3.0リリースやdevブランチ からのフォークによる新展開
  • 他組織やコミュニティ主導での 独自発展
  • メモリアロケータ分野 での新たな挑戦と進化

Hackerたちの意見

素晴らしい成果と未来におめでとう!jemallocは多くのメモリアロケーターにとってインスピレーションだったね。

jemallocが登場した頃、FreeBSDを使ってたんだけど、そのlibcの一部を入れ替えることを想像するだけで驚いたよ。正直、そんなこと考えたこともなかったし、他に何を一気に置き換えられるか考えさせられた。

へぇ、面白いね。jemallocはredisをはじめ、いろんなプロジェクトで使われてるメモリアロケーターなんだ。もしアロケーターを変えなきゃいけなくなったら、パフォーマンスにどんな影響が出るんだろう?

なんで変えなきゃいけないの?ソフトウェア開発って、時には「もう終わった」って感じになることもあるし、ライブラリにあんまり手を加える必要がないこともあるよね。

Firefoxもそうだね。

ダグ・リアが再び火を灯して、現代版のマルチスレッドdlmalloc2を作ったらめっちゃクールだよね!

dlは今、オープンJDKのガバナンスボードでただの観察者だから、時間はあるかもね。

最近、サービスが数日ごとにOOMになるメモリ断片化の問題を解決するためにjemallocを使ったんだ。今のjemallocはそのまま使えるけど、将来的にはどのアロケーターを選ぶべきか悩んでる。tcmallocや、標準のglibcよりもパフォーマンスが良いことを目指している他のアロケーターについて、経験がある人いる?

snmalloc

mimallocはいい選択だね。CPythonも最近mimallocに切り替えたし。

mimallocを試してみて。mimallocの上に機能をプロトタイプしたことがあるけど、努力が無駄になったものの、コードは(2020年頃の話)きれいに書かれてて、よくメンテナンスされてたし、ハッキングするのが楽しかった。jemallocをシステムでmimallocに入れ替えた時、断片化の成長制御やヒープ使用の観点では同等かそれ以上だったよ。

jemalloc以外に、macOSのmalloc/freeをLinuxのLD_PRELOADみたいにシームレスに上書きできるアロケーターはないと思う(少なくとも2020年頃まではね)。jemallocはすごくいいゾーンベースの方法でデフォルトに設定できるし、Appleの変わったアロケーターの要件にも対応できてるから、他のサードパーティのアロケーターがmalloc/freeを上書きしようとしたときに躓くことがないんだよね。

mimallocはここでも使えると思うけど、間違ってるかもしれない。

いい投稿だね。じゃあFacebookはjemallocを全く使ってないの?それともメンテナンスモードなのかな?最近はtcmallocとか他のアロケーターを使うこともできるんじゃないかと思う。Facebookのインフラエンジニアリングはコア技術への投資を減らして、投資収益率を重視してるみたいだし。

私がMetaを離れた約2年前(今でもそうだとは思うけど)には、Jemallocがアロケータで、会社で動いているすべてのバイナリに静的にリンクされてたよ。 > 今はtcmallocや他のアロケータを使えるんじゃないかと思うけど? Jemallocはそこに非常に深く統合されているから、思っているよりもずっと難しいんだ。Strobelightで流れているテレメトリから、すべてのアプリケーションがJemalloc特有の拡張を使っていること(例えば、カスタムエクステントフックを持つ手動で作成されたアリーナ)、そしてアプリケーションがJemallocの正確な動作に最適化されるように書かれていることまで、すべてが絡んでいるんだ。

Metaにはまだ開発が続いているフォークがあるよ。 https://github.com/facebook/jemalloc

これについて前から疑問に思ってたけど、知ってる人の周りでは聞いたことなかったな。外部から見ると、jemallocはglibcのmallocよりも明らかに改善されてるように見えたし、そういうベンチマークも見たことあるから、なんでデフォルトのアロケーターじゃないのか不思議だよね。

知ってる限りでは、jemallocがデフォルトのアロケーターじゃない技術的な理由はないよ。実際、記事にも書いてある通り、FreeBSDではデフォルトのアロケーターなんだ。私の理解では、これは主に政治的な理由だと思う。

こういうアロケーターは、しばしば起動コストが高いんだよね。安定した状態での高パフォーマンスを目指して設計されてるけど、Unixスタイルで短命なプロセスを百万個立ち上げるようなワークロードでは逆に悪化することもある。

注意:私はアロケーターエンジニアじゃないから、これはただの体験談だよ。前にOSのアロケーターを維持しているエンジニアと話したことがあって、その人の主張は、カスタムアロケーターは一つのプロセスのメモリアロケーションを速くする代わりに、システム全体のパフォーマンスを犠牲にすることが多いってことだった。システムアロケーターは、全体的に公平にアロケーションを行うのが難しいんだ。なぜなら、一つのプロセスが他のプロセスと同じパターンに従っていないから。だから、サービスに関しては、一般的に一つのプロセスが他の全てよりも優先されるべきだとよく勧められるんだよね。

長い間、代替アロケーターの大きな問題の一つは、OSに空きメモリを返さず、プロセス内にダーティページを保持し続けることだったんだよね。これも最終的には変わったけど、異なる優先順位を示す強い指標のままだよ。あと、多くのプロセスは単一スレッドしか持ってなかったり、せいぜいあまり興味深くないバックグラウンドスレッドが数個あるだけなんだよね。だから、これらの「マルチスレッド優先のアロケーター」は実際には価値のあるものを得ていないし、オーバーヘッドも多いんだ。関連する話として、多くの人が考えないことが一つあるんだけど、カーネルがメモリのページをゼロにするのと、ユーザープロセスが内部再利用のためにゼロにするのは、実は同じ量の作業なんだよね(将来のmmapの準備として)。

上流のリポジトリをアーカイブする決定には理解があるよ。私がMetaを離れた時点では、Jemallocチームは、みんながGitHubに投稿するランダムな問題に対応する余裕があまりなかったからね(特に、テストスイートがItaniumで通らなかったっていう問題が投稿された時は笑った)。それでも、こういうのを見ると悲しくなるよ。Jemallocは今でも、使いやすい一般的なmalloc実装の中で最高のパフォーマンスを発揮していると思う。TCMallocも素晴らしいけど、bazelを使わないと本当に使いにくいんだよね(最近、bazel 7.4.0でcc_static_libraryが追加されたから、静的ライブラリを少し簡単にエクスポートできるようになったけど、全体的にはその点は変わらない)。Qiに、再アーカイブする前に最終的な6.0リリースを作ることにオープンかどうか聞こうと思ってたんだ。それに、最終リリースのデフォルト設定を現代化するのもいいかも。デフォルトで(ちょっと混乱を招く名前の)「cache oblivious」設定を無効にして、16 KiBのサイズクラスが20 KiBに膨れ上がらないようにするのは大きな改善になると思う。これはJasonの元々の選択を貶めるつもりじゃないけど、確か最後にQiとDavidと話した時、あなたがこのデフォルトを選んだ時点では、典型的なTLBのアソシアティビティは今よりずっと低かったって言ってたよね。同じように、デフォルトの「ページサイズ」を4 KiBからもっと大きいサイズ(たぶん16 KiB)に増やすことで、大きなサイズクラスのカットオフ(つまり、アロケータが複数のアロケーションをスラブに配置するのをやめて、個々のアロケーションを直接自分のエクステントでバックアップするポイント)を16 KiBから64 KiBに引き上げるのもかなり影響があると思う。Metaを離れる前に最後に見たことの一つが、主要なサービスのためにこの変更を内部で行うことだったんだ。これで数パーセントのCPU改善が見込めたから(ただし、断片化が増えることでRAM使用量が少し増えるけど)。他にもいくつか調整したいことがある(例えば、metadata_thpのデフォルト設定を「disabled」から「auto」に変更したり、スラブのエクステントサイズをサイズクラスに合うページサイズの最も近い正確な倍数を使うのではなく、断片化を減らすために約1%の無駄なスペースを保証するようにすること)けど、上記の設定が一番大きいと思う。

こういうのがあるから、またここに戻ってくるんだよね。投稿してくれてありがとう!bazelを使わないとTCMallocの何が難しいの?(難しいとは言わないけど、純粋に興味があるから聞いてるよ。)

Itaniumのテストスイートの失敗を報告したのは私だよ。 :)

mimallocについて何か意見ある?

これらの変更を見てみたいな。もしくは、理由を説明するブログ記事や詳細なドキュメントがあればいいなと思ってる。今のドキュメントはちょっと寂しい感じがするし、Metaで内部的に行われた作業から得た知識が、今のうちに共有されるのが一番いいと思うんだ。

jemallocに切り替えたら、ずっと気になってたメモリリークが一瞬で解決したよ。ありがとう、敬意を表します!

それは、glibcじゃない健全なアロケーターは、未使用のメモリを定期的にOSに返すのに対して、glibcはそのメモリを永久に保持するのを好むからなんだよね。

ありがとう。最近、Javaの最適化に関するプレゼンテーションでJemallocを勧められたんだけど、使っている企業からちゃんと得られるものはあったのかな?大手テック企業は無料ソフトウェアを使うだけで、何も返さないことが多いから、ここでは例外であってほしいな。