JRuby - Java powered Ruby implementation

Java仮想マシンで動作するRuby「JRuby」はJavaがもっているさまざまな利点を利用できる興味深いプロダクトだが、Javaの欠点もそのまま引き継いでいる。もっとも顕著なのは起動時間が遅いことだ。これはLinuxなどでは特に気になる。一度起動が完了しJITが有効になったあとのJRubyは優れた速度を出すが、起動は遅い。小さいツールを作って利用するようなケースはJRubyは苦手ということになり、「起動が遅い」という悪名高い評判を得ることになる。

JRubyの主要開発者であるCharles Nutter氏がHeadius: JRuby Startup Time TipsにおいてJRubyの起動を高速化するための7つのテクニックを紹介している。いくつかはJRubyに特有のものだが、それ以外はJavaで動作するアプリケーションに普遍的に適用できる内容になっている。紹介されている起動高速化テクニックは次のとおり。

1. JVM動作モードの切り替え

OpenJDKおよびSun JDKはJITエンジンにHotSpotを実装している。HotSpotにはJITコンパイルが高速なかわりに最適化が浅い『client』モードと、長いパスとより多くのリソースを使うものの広範囲に渡って最適化を実施する『server』モードの2つが用意されている。起動時間を高速化するには『client』モードへの切り替えが有効。

従来のJDKはデフォルトで『client』モードを採用していたが、最近のJDKはデフォルトが『server』になっていることが多い。それに64ビットモードには『server』しか用意されていない。『server』モードで動作しているならオプションを指定して『client』モードに変えることで起動時間の大幅な短縮が可能。JRuby Startup Time Tipsに掲載されているRubyGemのブートとActiveSupportのロードという例では次のように起動時間が半分まで短縮されている。

Java jruby -e "require 'rubygems'; require 'active_support'"
JDK 1.6.0_17 64bit Server 5.068
JDK 1.6.0_17 32bit Client 2.275

HotSpot『client』モードと『server』モードによる起動時間の違い(横軸は実行時間[秒]) - Headius: JRuby Startup Time Tipsより抜粋

2. JVM共有アーカイブ機能

J2SE 5.0から共有アーカイブと呼ばれる起動高速化技術が用意されている。もともとAppleで採用された技術で、共有メモリにJavaクラスをまとめて読み込んでおくことで処理を高速化している。最近のMac OS XやWindowsではデフォルトで有効になっているため、これらOSでは起動速度が早い。

LinuxのようなOSではこの機能はデフォルトでは有効化されていないことがほとんどであるため、この機能を有効にすることで起動時間の短縮が可能になる。有効にするためのコマンド例は『sudo java -Xshare:dump』など。環境によっては-d32や-clientといったオプションも必要になる。

3. JRuby JIT機能の無効化かまたは遅延化

JRuby自身のJIT機能は、短い時間しか実行されないプログラムの場合には逆に速度を遅くする原因にもなる。このため、小さいプログラムの場合は『-X-C』を指定するなどしてJRuby JITをすべて無効にすることで起動時間が高速化できることもある。それほど劇的な向上にはならないが、積算されればそれなりの時間になる。

Java Time
jruby -S gem install rake 13.188
jruby -X-C -S gem install rake 12.590

小さいプログラムではJRuby JITを無効にした方が高速になるケースもある(横軸は実行時間[秒]) - Headius: JRuby Startup Time Tipsより抜粋

4. サブプロセスの生成を避ける

Rubyではクリーンな環境が欲しい場合など、#systemや#exec、またはバッククォートで括ることで処理をRubyのサブプロセスへ切り分けるテクニックがよく使われる。JRubyでは同テクニックが使われた場合でもパフォーマンスが落ちないように同じJVMプロセスに新しいJRubyインスタンスを生成して利用するといった工夫をしているが、それでもコストはかかる。また、対象にリダイレクトが含められている場合にはJRubyインスタンスの生成ではなく、新しくJVMプロセスを生成する必要がありとても大きな負荷になる。サブプロセスを生成するような処理は避けた方が起動時間を短縮できる。

5. 起動時の処理を減らす

なかなか難しいことだが、起動時に必要になる処理を減らすことで起動時間に短縮につなげることができる。

6. Nailgunを使う

Nailgunライブラリを使う。Nailgunは複数の実行を単一のJVMで実行するためのライブラリで、小さいJRubyコマンドライン実行などが劇的に高速になるという特徴がある。ただし、RubyGemやRails、サブプロセスを生成するようなシーンではあまりその強みを発揮することができない。

7. JVMオプションを活用する

JVMには多くのオプションが用意されており、それらは起動時間の短縮やまたその逆に効果を及ぼすものがある。特にヒープサイズやGCフラグのオプションには注意が必要。my favorite JVM flagsなどの記事を参考に調整するといい。なお、起動が高速になるパターンが見つかった場合にはJRuby開発者へ連絡してほしいと説明がある。またほかにもボトルネックを発見した場合にも連絡して欲しいと説明がある。