Ruby 2.0.0 の GC 改善

書いた人: nari (@nari3)

はじめに

何はともあれ、Ruby 2.0.0 のリリースおめでとうございます!!

本記事では無事にリリースされたであろう Ruby 2.0.0 における GC の進化ポイントを駆け足で紹介したいと思います。本記事で述べる「Ruby」とは「CRuby」、つまり C で書かれた Ruby 言語処理系を指すということでお願いします。

ビットマップマーキング (Bitmap Marking)

「Bitmap Marking」という機能を入れました。 簡単に説明すると、fork(2) で利用される Copy on Write (CoW) と相性をよくするための機能です。 具体的な内容は以下の記事を見てもらうのがよいでしょう。

Bitmap Marking がなく、fork(2) を使いまくる環境では、Ruby の GC が数回走ることでプロセスの使用メモリ量が爆発的に増えることがあり、「なな、GC とはメモリを減らすものじゃないのか? GC め、ぐむむ」と疑問に思われる事案もあったとのことです (ほんとすみません)。

また、fork(2) なんてものがない Windows 環境においては、Bitmap Marking の恩恵は受けられないのですが、それほど GC の速度が落ちなかったというのと、余分な複雑さを回避するという理由から Windows 環境でも Bitmap Marking を有効にしています。

さて、この機能はそもそも Ruby Enterprise Edition (REE) に導入されている機能です。 Ruby 2.0.0 には、REE と同等、いやそれ以上の性能を持つ Bitmap Marking が実装されています。 REE を選んだ理由が「Bitmap Marking」だけであるなら、すぐに出荷された Ruby 2.0.0 をインストールし、移行作業をはじめることをお勧めします。 ちなみに REE は 2012 年 2 月 から更新がおこなわれておらず、公式ブログでも終了を宣言しており、その役目を終えようとしています。 今までありがとう、REE。

非再帰的マーキング

いままでの Ruby では、マシンスタックを利用した再帰的な関数呼び出しによって、オブジェクトのグラフを辿りつつ、マークしていました。 ただし、とんでもなく深いオブジェクトグラフができてしまうと、マシンスタックが尽きてスタックオーバーフローしてしまいます。 そのため過去の Ruby の GC では「あ、スタックが溢れそう」と判断するとそれ以上はマシンスタックを使わない方法でマークをおこなっていました。

ですが、これには二つの問題がありました。

  1. 参照関係の深いオブジェクトが多くあると異様にマーキングが遅くなる
  2. スタックオーバーフローを検知する関数の精度が悪い

  3. についてですが、フェイルセーフであるマシンスタックを使わないマークの方法が最悪の場合「ヒープを全走査」なので、ワーストケースが結構遅いです。 しかも、参照の深いデータ群が生き残り続ける限りずーっと GC が遅いままになってしまいます。

  4. についてですが、スタック溢れのチェックが、速度との兼ね合いもあり、正確にチェックすることが難しく、たまーに SEGV が出ることがあったのです。 たとえば、運が悪いと Fiber が予期せぬタイミングで落ちたりしていました。

そこで、Ruby 2.0.0 からは、配列ベースのスタックを自前で用意し、それを利用して非再帰的(再帰的な関数呼び出しをせず)にマークするようにしました。 この方法だとマーク時にマシンスタックを消費しなくなり、オーバーフロー自体が起きなくなるので、1. で示したオーバーフロー時のフェイルセーフ機構が必要なく、2. で示したチェック処理も不要となります。 こうして 1.、2. の問題が解決しました。

ベンチマークを取ってみたところ若干速くなっているのかなという印象を受けます。 関数呼び出しが減ったのが効いているのでしょうか。

もっと詳細な情報が知りたい方はこちらも参考にしてください。

まとめ

Ruby 2.0.0 の GC 改善について駆け足で紹介してきました。少しでも参考になれば幸いです。

最後になりますが、Ruby の GC 界隈(どこそれ)でホットな話題をいくつか紹介しておきます。 まず、ささださんが世代別 GC 導入という大ネタを持っているようです。これには目が離せません。 また、立て続きに起きているシンボルに関する脆弱性(たとえば Rack の脆弱性)に端を発して、ささださんがシンボルの GC 実装に興味を持ったようです。 さらに、TracePoint.trace (2.0.0 から入った機能) の引数として :obj_alloc、:obj_free みたいなものを入れられないかなあと著者の脳内では検討がはじまっています。 これがあるといろいろとデバッグしやすくなるので、暇を見つけて実装してみたいものです。

著者について

nari。 ただの GC 好き。 ルンバが欲しいと思うも、部屋のルンバビリティが一向に高まらないので買えないでいる。

URL: http://www.narihiro.info/, Twitter: @nari3