12月25日にリリースされたRuby 2.2.0は,ルビーストたちへのクリスマスプレゼントだ。そのハイライトの中には,ガベージコレクション(GC)の改良がいくつか含まれる。新しいインクリメンタルGCアルゴリズムがあり,シンボルがガベージコレクションの対象になった。コアクラスと標準ライブラリにも,いくつかのマイナーな改良が加えられている。
スループットの大幅な改善を達成した,Ruby 2.1.0での世代別ガベージコレクション導入に続いて,Rubyのメンテナたちは,この領域において,重要な変更の導入を続けている。世代別GC(RGenGC)とは,ほとんどのオブジェクトが短命であるという前提のもとに,オブジェクトを世代別に分類するものだ。この仮定によって,古いオブジェクトはメモリが枯渇するまで削除の可否を判定されなくなるため,若いオブジェクトのスループット向上と停止時間の低減が実現される。しかしながらこれは,古いオブジェクトは依然として停止時間の長さに悩まされる,ということでもある。
世代別GCをベースに実装されたインクリメンタルGC(RIncGC)は,同等のスループットを維持しながら,停止時間を削減することを目標としている。インクリメンタルGCでは,Rubyが定期的に実行している,オブジェクトにGC対象のマークを付けるマークフェーズをインターリーブすることで,停止時間の短縮を実現する。これまでのRubyでは,マークフェーズは一括して実行される,巨大な処理ステップになっていた。
RGenGCとRIncGCでは,どちらもすべてのオブジェクトを管理することはできない。つまり,いくつかのオブジェクトは古い世代に昇格することはない,ということだこれは主としてC言語拡張が,RGenGCとRIncGCの求める制約のすべてを保証することができない,という事情によるものだ。Koichi Sasada氏がRubyConf2014で,RGenGCとRIncGCに関して詳細に説明している。すべてのアルゴリズムに関する詳細とパフォーマンスベンチマークを知りたい向きには,興味深い資料だ。
Stop the World GC 対 インクリメンタルGC. 出典: Koichi Sasada.
RIncGCによる長時間停止の回避 出典: Koichi Sasada.
文字列識別子の一種であるシンボルへのGC導入も,Rubyのメモリ管理を改善している。そういった事情から,2015年秋を目標とするRuby on Rails 5.0では,動作対象を,これら変更が実施されたRuby 2.2以降に限定する予定だ。
Rails 5.0では,Ruby 2.2+のみを対象とします。Ruby 2.2を素晴らしいものにしてくれそうな最適化はたくさんありますが,Railとして最も重要なのは,シンボルがガベージコレクションの対象になることです。このおかげで,外部から入力を受ける時に必要な,文字列の操作に関する重荷を大幅に落とすことができます。同時にそれは,最新のRubyが提供するキーワード引数や,その他の優れた機能にすべて乗り換えることが可能になる,という意味でもあるのです。
これまでは,シンボルがガベージコレクトの対象になることはなかった。Rubyの内部では,それぞれのシンボルが整数値にマッピングされていたからだ。CRuby – RubyはC言語で記述されている – では,この整数値がシンボルの識別に使用されていた。もしRubyのシンボルがGC可能であったならば,後になって再生成された時に,別のCRuby整数IDを持つことになる。これはつまり,Rubyの言語仕様の上では同じシンボルが,実効的には別のものになる,すなわちバグということだ。
簡単な解決方法は,CRubyの整数を文字列で置き換える,つまり2つの世界(CとRuby)を統一することである。ここでもまた,C拡張が問題を複雑にしている。ランタイムがすべてのシンボルの検出と管理を行うのを妨げているのだ。これを解決するために,シンボルをイモータル(immortal)とモータル(mortal)の2つのグループに分けた。イモータル(不滅)のシンボルには,引き続き整数のIDを使用する。従ってGCの対象にはならない。メソッドの名称,変数名,定数,その他の言語要素がイモータルシンボルの例だ。モータルなシンボル,例えば"foo".to_sym
は,整数IDを持たず,従ってガベージコレクションが可能になる。
モータル対イモータルシンボル 出典: Narihiro Nakamura.
RubyKaigi2014ではNorihiro Nakamura氏が,シンボルGCソリューションと,そこに至ったすべての理由について説明している。
メモリ管理の面で,Ruby 2.2.0にはさらに,システムのmallocに代えてemallocを使用するオプションが用意されている。これによって速度の向上と,メモリ断片化の減少が期待できるが,より多くのパフォーマンスデータとユースケースが収集できるまでは,実験的機能という位置付けだ。
system()やspawn()といったプロセス生成メソッドでは,可能であれば,fork()に代えてvforkが使われるようになった。この変更により,特に親プロセスが多くのメモリを使用している場合のパフォーマンス向上が期待できる。 これも同じく実験的機能であるため,将来的には変更される可能性がある。
コアライブラリがUnicode 7.0をサポートするようになった。さらにEnumerable#slice_after,Enumerable#slice_when,Float#next_float,Float#prev_float, File.birthtime, File#birthtime,String#unicode_normalizeなど,いくつかの新機能が含まれている。
Ruby 2.2.0ではmathライブラリが非推奨になった他,いくつかのライブラリが更新されている。
- Psych 2.0.8
- Rake 10.4.2
- RDoc 4.2.0
- RubyGems 2.4.5
- test-unit 3.0.8
- minitest 5.4.3
非推奨になったC言語APIや重要な変更に関しては,Ruby 2.2.0 NEWSの記事に詳細な解説がある。Ruby 2.2.0では,Ruby 2.1.0に対して1,557のソースファイルが更新され,125,039行が挿入,74,376行が削除されている。