まえがき (ポエムなので読み飛ばせます)
「Rubyは遅い!」と言われがちですが、来たる Ruby3.0 に向けて、今までよりも3倍の高速化を図る試み、 Ruby 3x3
が進行しています。
先月行われたrubykaigi2019でもその取り組みが発表され、多くの期待の眼差しが向けられていました。
とりわけ、高速化の肝になるであろう JITコンパイラ
はセッションだけでなくキーノートでも触れられ、大きな注目を集めていました。
しかし、電子計算機の仕組みを独学で(しかも基本情報技術者試験のため)ふわっと勉強した筆者(非情報系卒)にとっては、いささか難しい内容で、オープンされるスライドのたびに変わる会場の空気を「完全に理解した」とは口が避けても言えませんでした。
それでも、JITコンパイラ
をはじめとする数々の試みが、Rubyエンジニアにとって、とても興味深く、同時に楽しいことであることは十分に伝わってきました
今回はrubykaigi2019の残り香を楽しみつつ、多くのセッションでも取り上げたれたJITコンパイラについて、詳しく見ていきたいと思います。
この記事では、JITの概念をふわっと理解するために、ふわっと理解しようとしている筆者が書いています。
もし、誤りや怪しい部分がございましたら、忌憚ないご意見をいただければ幸いです! (>_<)
JITコンパイラ is なに?
JITコンパイラ
は Ruby2.6 からオプションで追加された、Rubyを高速に実行しようとする仕組みです。
JIT(Just-In-Time Compiler)とは、コードがまさに実行されるそのときにコンパイルされる仕組みのことで、RubyだけでなくJavaの実行環境でも取り入れられている仕組みです。
JITコンパイルという用語は、ソフトウェアを構成するモジュールやクラス、関数などの、ある単位のコードがまさに実行されるその時に、コンパイルされることから「Just In Time」の名前が付けられた
wikipediaより https://ja.wikipedia.org/wiki/%E5%AE%9F%E8%A1%8C%E6%99%82%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9
RubyのJITはMJIT
という名前がよく出ていますが、しくみを理解する上ではRubyのJITの実装周りの総称として覚えておいて良さそうです。
参考:https://k0kubun.hatenablog.com/entry/ruby26-jit
これまでとの処理の違い
Ruby1.9~Ruby2.5 ,デフォルト設定のRuby2.6での実行
Rubyはインタープリタ型言語なので、コンパイルして直接機械語に変換されるわけではありません。どうやって実行されているというと、Rubyのコードは字句・構文解析を経てYARVバイトコード
というものに変換されます。バイトコードはプログラム言語と機械語との中間にあたるようなコードです。
でもYARVバイトコード
はあくまでバイトコードであって機械語ではないので、CPUはこの YARVバイトコード
を直接解釈して実行することはできません。
そこで、CPUに変わってYARVバイトコード
を解釈し、CPUに命令を発行してくれるのがバーチャルマシン(VM)であるYARV
です。
こうしてRubyのコードは、明示的な機械語へのコンパイルを必要とせずに、実行することができます。
MJITを有効にしたRuby2.6
(これらの理解の拠り所として Cコンパイラを利用したRubyのJITコンパイラ を参考にさせていただきました。)
YARVバイトコード
が生成されるところまではこれまでと変わりません。
そして、バーチャルマシンであるYARV
もこれまで通り登場しますが、JITコンパイラー
という役者が増えています。
あるプログラムが実行されたとき、YARV
(以下VM) は生成された YARVバイトコード
を解釈し、CPUが理解できる命令を発行し、実行してくれます。
ここで、あるメソッドが5回以上呼ばれたとします。そのときVMのスレッドは、JITのキューに、このメソッドを積みます。
JITはVMとは別のスレッドで動いて、積まれたメソッドをYARVバイトコード
からCのコードに変換します。
生成されたCのコードはやがて機械語
に変換され.soファイル
が生成されます。
.soファイル
の中身はバイナリコード(=機械語)です。これは動的にVMから呼ばれるようにリンクされます。
なので、もし次のタイミングでVMが処理しようとしているYARVバイトコード
の中に、先ほどJITコンパイラ
が処理したのと同じメソッドがあった場合、
VMはYARVバイトコード
を解釈してメソッドを実行するのではなく、機械語にコンパイル済みのメソッド.soファイル
を関数ポインタを通じて読み込むことで、より高速に同メソッドを実行することができます。
現状での速さ/ベンチマーク
JITの仕組みによりRubyの高速化が図られたわけですが、いったいどれだけ早くなったのかというデータは検証方法によってバラツキがあるようです。
これはCコードを生成する際の最適化、Cコードから機械語に翻訳する際の最適化など、様々な要素が絡みあっているからのようで、単純に「何倍早くなった!」と言えるわけではないようでした。
また、JITを有効にしてRuby on Rails
で作成したWebアプリケーションを実行すると、かえって遅くなるようなデータもrubykaigiでは示されていました。
ただ最新の実験ではJITを有効化しても、無効化時と同程度のスコアが出るようにはなったそうです....ここからが本番。(2019/4 rubykaigi)
(これは筆者の感覚的理解ですが、単純に処理が増えたのだからそれは遅くなっても仕方なさそうだし、プログラムの実行時間が長くなり機械語にコンパイル済みのメソッドが増えれば増えるほど高速化してゆくようなパラダイムでもあるし....納得、という感じです。)
さらに、JIT以外の速度改善についても、「rubyのインタープリタをrubyで書く」といった内容があり、非常に興味のそそるものでした。RubyKaigi 2019: Write a Ruby interpreter in Ruby for Ruby 3
おわりに
rubykaigiを振り返ると自分はいかにRubyを知らなかったのか思い知らされます。日常的にRailsに触っていると、Railsが行ってくれている魔術が当たり前になってしまうことがあったかもしれません。
一方、周りを見渡すと、Railsを使いながらもRubyで内製ツールを作って開発効率を上げている例が多くあることを知り、そういった活動が組織の技術力を作り、またOSSの活動へと広がってゆくことを実感しました。
Rubyというプログラミング言語を通し、技術に向き合うとうことを再確認したrubykaigiの3日間でした。
参考にた書籍/Webページ
Rubyのしくみ -Ruby Under a Microscope-
Ruby 2.6にJITコンパイラをマージしました|k0kubun's blog
プロと読み解く Ruby 2.6 NEWS ファイル|クックパッド開発者ブログ
Cコンパイラを利用したRubyのJITコンパイラ / Programming Symposium 60
LITALICOではエンジニアを積極採用中です。
新卒・第二新卒(未経験含)/中途採用、いずれも行なっていますので、ご興味のある方は下記URLをご確認ください。
https://www.wantedly.com/projects/309158