Rubyの処理系とGCについて

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

冬休み中にRubyのGCについて知ろうと思って「Rubyのしくみ -Ruby Under a Microscope-」を読んだので、内容をまとめておきます。なお、本書はRubyの処理系の内部を詳細に解説した本なのですが、この記事ではGCを理解するのに必用な知識だけを書きます。

Rubyの処理系について

RubyのGCについて知る前に、「処理系」というものについて理解する必要があります。なぜならGCは処理系によって異なるからです。

プログラム言語における処理系とは

よくある例として、「MRIはC言語で実装されたRubyの処理系です」みたいな文章があります。ここで言う処理系ってなんでしょうか。処理系はその実装方法によって構成が異なるため、どこまでを処理系と言っているのかだいぶ頭が混乱しました。正確な定義は分かりませんが、英語に直すと LANGUAGE PROCESSING SYSTEM あるいは単に LANGUAGE PROCESSOR となるので、何らかのプログラム言語で書かれたソースコードを実行するものと理解して良さそうです。

標準の処理系

Rubyの標準の処理系はMRI(Matz' Ruby Implementation)で、C言語で書かれているのでCRubyとも言うようです。Ruby1.8までは、C言語で実装された処理系がRubyのソースコードを読み込みevalにより実行していました。Ruby1.9以降はこれにYARVが追加されました。YARVはRubyのソースコードをYARV命令列(アセンブリ言語のような感じ)に変換し、evalではなくYARV(VM)により実行するようになりました。中間コードを挟むことで実行速度の向上を図っています。

その他の処理系

他にメジャーな処理系として、JRubyやRubiniusが紹介されています。JRubyはJavaで実装された処理系で、RubyのソースコードをJVMで実行可能な中間コードに変換し、JVMにより実行されます。これにより、Javaのライブラリを使用したり、RubyでありながらJVMの恩恵を受けることができます。RubiniusはRubyで実装されたカーネルとC++で実装されたVM(LLVM)による処理系です。RubyのソースコードをLLVMが解析可能なコード(LLVM IR)に変換し、LLVMのJITコンパイラにより実行されます。

以下のページにはその他の処理系も紹介されています。

Rubyアソシエーション: Ruby処理系の概要

RubyのGCについて

MRIのGC

MRIのGCはマーク・スイープというアルゴリズムが使われています。マーク・スイープは、あらかじめ用意されたメモリ領域(ヒープ)をオブジェクトに順に割り当てていき、ヒープが足りなくなったらプログラムを停止し、オブジェクトがまだ生存しているかをマークしていき、マークされていないオブジェクトを削除していく(これをスイープという)、という動きをします。また、Ruby1.9.3以降は、遅延スイープというGCによるプログラムの停止時間を短くする手法が取り入れられ、Ruby2.1からは世代別GCという新しいオブジェクトと古いオブジェクトでGCを分ける手法が取り入れられたようです。

チューニングのための設定については以下に記載されています。

module GC (Ruby 2.4.0)

JRuby, RubiniusのGC

JRubyはVMがJVMですから、JVMのGCが使われます。RubiniusのVMのGCもJVMと同様のアルゴリズムで実装されているそうです。JVMのGCはコピーGC、世代別GC、平行GCというアルゴリズムが使われています。今はMRIだけを知りたいので、これらの詳細については割愛します。

終わりに

GCの仕組みについてはすっごい薄いですが理解ができました。世代別GCさえ抑えておけば、チューニングの項目の意味は理解できそうです。

次はアプリケーション内のメモリのモニタリング(本来やりたかったこと!)と必要があればGCのチューニングについて実践したことを書きたいと思います。

参考