2009年12月1日火曜日

JRubyのJはJVMのJ

原文:トム=エネボ

現在JRubyの開発に携わっている面々は皆Ruby、Java、そして勿論JRubyを熟知している情熱的なハッカーばかりです。とは言うものの、このプロジェクトの創成に関わった者は一人も居ません。JRubyの先駆者たちはそれがいいアイディアだったと考えたに違いありません—私もJRubyのことを初めて耳にした時そう思いました。ところが多くの人にとってはこれはそう自明の事では無いのかも知れません。そういう人たちは「JVMの上でJRuby書いていい事なんかあるのですか」と訊きます。僕らJRubyチームはちょっとネジが外れてるか、狂気の天才なのか、それともJVMを使うのは至極実利的な決断なのでしょうか。

Javaは登場した当時、存在していたプログラム言語をちょっと良くしたような物でした。Javaはそこそこ簡単であり、かつ既によく使われていたCやC++を進化させたような物であるにも拘わらず、ハードウェアに中立なバイトコードを走らせる事の出来る仮想マシンでした。ガーベージコレクションがJavaには実装されていたので、メモリ管理やコアダンプに大して気を捕らわれなくなりましたし、加えて中々総括的なライブラリを持っていました。特に目新しい物は無かったのですが、既にあったアイディアを上手い具合に組合わせて使える物になっていたと言えるでしょう。

これは私見ですが、このように二つの異なる物をJavaと言う一つのブランドで売ろうとしたSunのマーケット戦略は長い目で見ると失敗でした。この戦略では、プログラム言語とランタイムが全く同一の物であるかのように同じ名前で呼ばれる事になっていたのです。この戦略は、ランタイム(それ自体で充分に利点の有ったJava仮想マシン)にとって非常に不公平な物でした。更に、JythonやJRubyの様にJVMを基にしたプログラム言語が、プログラム言語としてのJavaとの混乱を招く事無しにJVMの良さを説明する事が出来なくなるという結果にもなりました。

本稿ではJVMの良い点と悪い点について触れることにして、JVMの姿を読者が垣間見る事が出来ると良いと考えます。

本題に移る前にもう一言。本稿で扱うのはSunのJVMについて私の知っている事に限っています。Sun以外からもJVMはリリースされていますし、それぞれに良さが有ります。私が以下に挙げる点の多くはそうしたJVMにも当て嵌ると思われますが、私がそうしたJVMの詳細に暗い事は断っておきます。


短所


完璧なテクノロジーは存在しません。JVMも勿論例外ではありません。JVM上でJava以外の言語の実装をした事のある人ならば、その苦労は解ると思います。プログラム言語実装に便利な基盤をJVMは提供してくれますが、元々はそうした用途を想定してデザインされている訳ではありません。その為、独創的な問題解決を余儀なくされる事が時としてあります。

例えば、JRubyではフレームスタックの実装にヒープ領域から割り当てられたオブジェクトを使っています。融通が利くのならば、本物のフレームスタックを直接操作し、JRubyに必要なフィールドを加えるところです。JVMではフレームスタックをそこまでは操作できません。残念。

他には、RubyでのFixnumオブジェクトを造る際、これらの値をボックス化してJavaのオブジェクトに内包させています。RubyのC言語での実装では単にタグの付いたintを渡すだけで済みます。もっと高速のFixnumがあれば良いのですが、Javaの原始型では一般的なリストに入れる事が出来ませんから、これを使う訳には行きません。

ちょっと差し挟んで言うと、JVMの起動にはかなりの時間が掛かる事も問題です。

JVMで速いコードを書かなければならない人の中には、VMの中の動きがよく解らない事に業を煮やした経験のある人も居るかも知れません。一度バイトコードをロードしてしまった後は、ただスイッチを入れて良い結果が出る事を祈るだけです。どのような結果が出るのか予測する事は難しいです。

小言を並べてみました(勿論この他にも改良されるべき点は色々とあります)が、JRubyがJVMの上で実装されていると言う事に私はとても満足しています。何故でしょうか。それは…。

長所


HotSpot


先ず始めにHotSpotについて述べます。HotSpotには神秘的な物がありますが、一般に高いパフォーマンスの実現に一役買っている事は間違いありません。動的にプロファイリングを行うのは高いパフォーマンスへの近道です。HotSpotは我々などより遥かに賢く、しかも走っているアプリケーションのデータを使って最適化の一助とする事も出来るのです。

HotSpotには既に最適化されているコードを非最適化する機能もあります。奇妙に聞こえるかも分かりませんが、これが高いパフォーマンスを可能にする要因なのです。HotSpotが最適化をする時、始めに最適化された部分のコードの前に簡単なガードを置いて、最適化の為に用いた前提条件が本当に満たされているのかを確認します。このガードが満たされなかったりした場合、HotSpotはこの部分のコードを非最適化します。何故これが凄いかというと、この性質の為にJVMは、最適化にとても積極的になる事が出来るからです。もしHotSpotの「勘」が当たると、その配当はとても大きいです。「勘」が外れても、今一つズレている最適化を試してみたという多少のコストを払って、後々の最適化に役立つ情報を手に入れた後、最適化していた部分を元に戻すだけで済みます。

このHotSpotの働きを示すのが下のグラフです。



このグラフに示すのはマンデルブロ集合を描く作業を何度か繰り返した、その所要時間です。グラフの右側でJRuby1.4.0を見ると、Ruby1.8.7を明らかに凌駕し、またRuby1.9.2preview2といい勝負をしているのが解ります。JRubyのグラフを追うと、Ruby1.8.7にテスト開始直後こそ後れを取っているものの、HotSpotがその機能を発揮するにつれて急速に追いつき追い抜いていく様が手に取るように解ります。

六度目のテストでJRubyの結果が少し悪くなっています。これは先に述べたHotSpotの非最適化の結果です。七度目以降は結果が五度目程度まで回復しているのも判るでしょう。七度目以降の最適化で六度目のテストで発生した非最適化の部分を総て取り戻す事が出来たのです。

HotSpotに匹敵するものを書く事は私には無理でしょう。数多くの優秀なエンジニアたちによって十年近くの歳月を掛けてHotSpotは磨き上げられました。実際、Java4、5、6でHotSpotは見事な進化を遂げました。JRubyはHotSpotを何もせずにタダで使う事が出来るだけでなく、HotSpotがアップグレードされる度にその恩恵を浴びる事が出来るのです。これはJVMを使えばこそ。完璧なテクノロジーなど無いとは言いましたが、これはかなりそれに近いと思います。これはまだ小手調べ。ガーベージコレクションに言及もしていません。

ガーベージコレクション


Javaの開発者たちは、それこそ数世紀に値する時間を掛けて仮想マシンをデバッグし、テストし、改良してきました。15年もの間、ただガーベージコレクション(以下GC)の為だけに働くエンジニアが詰まった建物を想像してみて下さい。Java仮想マシンには沢山のガーベージコレクタが梱包されていますから、一つのコレクタで上手く行かなかったとしても、JVMではその他のコレクタを使う事が可能です。更に、コレクタを一つ一つ調節する事も出来ます。アプリケーションによってコレクタを自由に使い分ける事が可能なのです。
コレクタの数々にはとても具合の良い属性があります。コレクタの総てはコンパクションを行いますからメモリの断片化は問題ではありません。インクリメンタルでもありますからGCが実行される際にアプリケーションが止まる時間の長さも最小限で済みます。世代別ガーベージコレクタですので、短時間しか必要のないオブジェクトは直ぐに回収されてしまいます。

コレクタの幾つかは並行型でマルチコアの特性を活かす事が可能です。中には同時処理が出来るのでGCの為にアプリケーションを止める必要のないものもあります。こうした素晴らしいコレクタ等も数多のアプリケーションで既にその性能を実証済みです。JRubyではこうしたもの総てをタダで使う事が出来るのです。Java 7(及びJava 6u12)ではG1と呼ばれる新しいコレクタも登場しています。JavaのGCは今も尚改良が続いているのです。

JVMにおけるGCについて良い点をもう二つ挙げます。どちらもGCの視覚化とどのように作動しているのかに関するものです。一つはJavaで使われる-J-verbose:gcというフラグです。このフラグを使用すると、いつ、どれ位のメモリを、どれ位の時間を掛けて収集したかというGCに関するイベントを総て記録してくれます。これを使うとアプリケーションにおいてコレクタが上手く作動しているかを把握する事が出来ます。


[GC 16000K->3727K(82496K), 0.0396636 secs]
[Full GC 13021K->5802K(82496K), 0.1468975 secs]
[GC 21802K->9769K(82496K), 0.0292348 secs]
[GC 25769K->12535K(82496K), 0.0243674 secs]
[GC 28535K->13136K(82496K), 0.0169928 secs]
[GC 29136K->15498K(82496K), 0.0213308 secs]
[GC 31498K->16911K(82496K), 0.0213301 secs]
[GC 32911K->19413K(82496K), 0.0186457 secs]
[GC 35413K->20207K(82496K), 0.0146396 secs]


記録されたイベントを集計する事によってメモリの収集に全体でどれだけの時間を要したかが判りますし、もしかしたらアプリケーションが課す作業量にGCが着いて行ってない事も判るかも知れません。そうした場合、アプリケーションのデザインの見直しや、ヒープ領域の拡大などを試したりする事にもなるでしょう。

もう一つはjconsoleによってJVMの動きを見る事です。jconsoleにはJVMの内部を覗く為の様々な機能(JVMの動作を操作するものも有ります)がありますが、ここで触れるのはGCの作業具合を見る事の出来る、メモリのタブです。



ウィンドウ右下に、それぞれの世代のメモリの使用量が緑色の棒グラフで表示されています。例えば、上の図で左から三番目の棒が空になっていますが、これはこの世代のメモリが全く回収されずに済んでしまった事を示しています。GCが上手く作動していない事の証左ですので、アプリケーションの改良を検討する必要が有るでしょう。

移植性


HotSpotとGCはJavaを使える環境ならばどこでも使えます。つまり、JRubyは他の環境でもただ単に動くばかりでなく、とても上手く動くのです。このような素晴らしい環境を何もせずに手に入れる事が出来るのは素晴らしい事です。

更にJRubyはちょっと変わった環境でもJavaさえあれば動きます。バグ報告の中にはOpenVMSからの物さえあります。IBMメインフレームのCP/CMSで動くJRubyの話も耳にします。こうした環境で動くというのはちょっと「ヤバい」気がします。これも偏にJVMのお陰と言えます。

実際、Javaのソースコードが公開されてOpenJDKプロジェクトの発足を受けて、JVMは今後も色々なハードウェア環境へと伝播して行く事でしょう。このように、移植性という点でJVMはほぼ完璧と言えると思います。

成熟性


JVMは開発が始まってから15年を経て、大人向け映画を観に行けるような歳ではないかと思います。JavaOneカンファレンスでは、JVMのソフトウェアとしての長寿が、子供が成人して行く様に正しく喩えられていました。過去15年にJVMの安定化と高速化と普及の為に注ぎ込まれた膨大な努力の恩恵に、JRubyは全く何もせずにあやかる事が出来るのです。

JVMチームとの連携


JRubyとその他のJVM上で動く言語は、JVMに携わるエンジニア達にも知られています。一つ例を挙げますと、JSR292としても知られるinvokedynamicという仕様があります。この仕様は、JVM上で実装される新しい言語と、メソッド選択にJava以外の言語も念頭に入れる必要が有る事を明文化するものです。JVMが、JRubyのようなプロジェクトにとってより良いプラットフォームになりたいと考えていると言う事は大変良い事です。

MLVM(多言語仮想マシン)と呼ばれるプロジェクトではJVMでどのフィーチャが採用されるべきかの情報交換が行われています。そこは、野心的なデベロッパがJVMでのフィーチャの実装を試す機会を与えてくれます。JVMのエンジニア達も直接関って、様々な提案もしてくれます。これは新しいJVMフィーチャの培養器と言った感が有ります。

この二つの点は、JVMエンジニア達が、視野を広げ、またユーザーの声に耳を傾けている事を示している事の現れであるということで特に喜ばしいです。

まだまだ続きます


JVMはテクノロジー展望の一部となりました。オラクルによるSunの吸収合併と、それに伴ういざこざをめぐって、中には首を傾げるようなニュースも飛び交っていますが、真相は至ってありきたりです。オラクル、IBM、HP、SAP等々、多くの巨大な企業がJVMで動くミドルウェアの創造に莫大な投資をしているので向こう十年程度でJVMへの拘わり方が変わるとは考え辛いです。

これにより、JRubyの更なる性能の向上が見込めます。加えて、Rubyような言語、或いはRuby on Railsへと市場を導く機会がJRubyには有ると言えます。JRuby開発チームは全力でRubyist諸氏のRubyランタイムへの要望を満たす努力をしています。同時に我々は、Javaを使う人々に、アプリケーションによってはRubyがJavaに優り、しかも使用するプラットフォームを換える必要のない言語である事を伝えているのです。

0 件のコメント:

コメントを投稿