第20章 Rubyの未来

解決すべき課題

rubyは「完成してしまったソフトウェア」ではない。まだまだ発展途上で あり、多くの課題を抱えている。まずは現在のインタプリタに内在する 問題を摘出してみよう。話題の順番はだいたい本書の章の順番に沿っている。

GCの性能

現在のGCの性能は「特に悪くないが、特に良くもない」と言ったところだろう か。「特に悪くない」というのは「日常生活で困ることはない」ということで、 「特に良くもない」というのは「高負荷になると弱点が露呈する」という意味 である。例えば大量にオブジェクトを作ってそのまま保持し続けるアプリケー ションだと速度が急激に低下してしまう。GCのたびに全オブジェクトをマーク することになるうえ、オブジェクトが回収できないものでさらにGCの回数まで 増えてしまうからである。この問題には第5章で触れた世代別GCが効 果的なはずだ(少なくとも理論的にはそういうことになっている)。

また反応速度の点でも改善の余地がある。現在のGCの実行中はインタプリタ全 体が停止するので、エディタだとかGUIアプリケーションだと時々「ぐっ」と 固まって反応が中断することになる。例えそれが0.1秒程度だろうとも、 文字をタイプしている途中に止まられたりすると非常に印象が悪い。今はそ ういうアプリケーションはあまり作られていないか、あってもさほど大きくな いためこの点があまり問題にならないのだろう。だがいずれそういうものが出 てくればインクリメンタルGCの導入を考える必要もあるかもしれない。

パーサの実装

第二部で見たようにrubyのパーサの実装は 既にyaccを限界近くまで酷使しており、これ以上の拡張に耐えるとは思えな い。拡張の予定がないのならいいが、この後には「キーワード引数」という 大物の導入が予定されているし、yaccの制限のせいで欲しい文法が表現でき ない、なんてことになったら悲しい。

パーサの再利用

Rubyのパーサは非常に複雑だ。特にlex_stateのあたりを真面目に扱う のがとても大変である。そのせいで、Rubyプログラムを埋め込んだり、 Rubyプログラム自体を扱うプログラムを作るのが非常に難しくなっている。

例えば筆者が開発しているツールでraccというものがある。yaccのRuby版 なのでRを付けてraccだ。そのraccでは文法ファイルの構文などはほとんど yaccと同じで、 アクションの部分だけがRubyのコードになっている。そのためにはRubyのコー ドをちゃんとパースしないとアクションの終わりを判定できないが、「ちゃん と」パースするのがとても難しい。仕方がないので今は「だいたい」パースで きるというレベルで妥協している。

他にRubyプログラムの解析が必要になる例としては indentlintのようなツールが 挙げられるが、こういうものを作るのにも非常に苦労する。リファクタリング ツールのような複雑なものになるともはや絶望的である。

ではどうしようか。同じものを作り直すのが無理なら、rubyのオリジナルの パーサを部品として使えるようにすればいいのではないだろうか。つまり処理 系のパーサ自体をライブラリ化するわけだ。これは是非とも欲しい機能である。

ただここで問題なのは、yaccを使う限りパーサがリエントラントにできない ということだ。つまりyyparse()を再帰呼び出ししたり複数のスレッドから 呼んだりできないのである。だからパース中にRubyに制御が戻らないように実 装しなければならない。

コード隠蔽

現在のrubyは動かすプログラムのソースコードがないと動かせない。 つまりソースコードを他人に読ませたくない人達は困るだろう。

インタプリタオブジェクト

現在のrubyインタプリタはプロセスに一つしか持てない、ということは 第13章で話した。複数のインタプリタを持つことが現実に可能 ならそのほうがよさそうではあるが、果たしてそういうことは実装可能なのだ ろうか。

評価器の構造

いまのeval.cはとにかく複雑すぎる。マシンスタックにRubyのスタックフレー ムを埋め込むのも何かと厄介の元だし、setjmp() longjmp()を使いまくるのも わかりやすさと速度を下げている。特にレジスタの多いRISCマシンだと setjmp()を使いまくると速度が落ちやすい。setjmp()ではレジスタを全て 退避するからである。

評価器の速度

rubyは普通に使うぶんには既に十分に高速だ。だがそれでもやはり、言語処理 系は速ければ速いほどいいのは間違いない。速度を上げる、即ち最適化をする にはどうしたらいいだろう。そういうときはまずプロファイルを採らねばなら ない。というわけで採った。

  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
 20.25      1.64     1.64  2638359     0.00     0.00  rb_eval
 12.47      2.65     1.01  1113947     0.00     0.00  ruby_re_match
  8.89      3.37     0.72  5519249     0.00     0.00  rb_call0
  6.54      3.90     0.53  2156387     0.00     0.00  st_lookup
  6.30      4.41     0.51  1599096     0.00     0.00  rb_yield_0
  5.43      4.85     0.44  5519249     0.00     0.00  rb_call
  5.19      5.27     0.42   388066     0.00     0.00  st_foreach
  3.46      5.55     0.28  8605866     0.00     0.00  rb_gc_mark
  2.22      5.73     0.18  3819588     0.00     0.00  call_cfunc

これはとあるアプリケーションを動かしたときのプロファイルなのだが、一般 的なRubyプログラムのプロファイルにもかなり近い。つまりトップに圧倒的割 合でrb_eval()が登場し、そのあとにGCと評価器中枢部、加えて処理に特有 の関数が混じる。例えばこのアプリケーションの場合は正規表現マッチ (ruby_re_match)にかなり時間がかかっているようだ。

ただそれがわかったとしてどう解決するかが問題だ。単純に考えれば rb_eval()を速くすればいい、ということになるだろうが、rubyのコアに 関しては小手先の最適化をやる余地はもうほとんどない。NODE_IFのところ で使われていたような「末尾再帰→goto変換」もほとんどやり尽くした感が ある。つまり根本的に考えかたを変えない限り向上の余地がないのだ。

スレッドの実装

これは第19章でも話した。現在のrubyのスレッドの実装は非常に 問題が多い。特にネイティブスレッドとの相性の悪さはどうしようもない。 rubyスレッドの(1)移植性が高く(2)どこでも同じ挙動、という二点は確 かに他に代え難い長所なのだが、さすがにあの実装はずっと使い続けるには無 理があるのではなかろうか。

ruby 2

続いて今度はこれらの問題点に対するオリジナルのrubyの動向を示す。

Rite

現時点でのrubyの最新バージョンは安定版が1.6.7、開発版が1.7.3だ。 だがそう遠くないうちに次の安定版1.8が出そうである。そうすると同時に 開発版の1.9.0がスタートする。そしてその次はちょっと変則的に1.9.1が 安定版となる。

安定版開発版開始時期
1.6.x1.7.x2000-09-19に1.6.0リリース
1.8.x1.9.0半年以内には出るだろう
1.9.1〜2.0.0二年後くらいか

そして次々世代の開発版がruby 2、コードネームRite、である。 この名前はLとRの区別がつけられない日本人へのオマージュらしい。

一言で2.0はどこが変わるかと言うと、コアほとんど全部だ。スレッド、評価 器、パーサ、これが全部変わる。もっとも、コードがカケラも出てきていない のであくまでここに書くのは全て「予定」である。あまり期待しすぎると失望 するかもしれない。そういうわけで、軽く期待、ということにしておこう。

記述言語

まず使う言語。間違いなくCだろう。Rubyの英語メーリングリスト ruby-talkでのまつもとさんの発言によると

I hate C++.

だそうなので、C++を使うというのはまずありえない。いくら全面作り直しと 言ってもオブジェクトシステムはほぼそのまま残ると考えられるので、そのあ たりでの手間が増えないようする必要もある。ただしCはCでも今度はANSI Cに なる可能性は高いだろう。

GC

GCの実装では、 まずBoehm GC\footnote{Boehm GC http://www.hpl.hp.com/personal/Hans_Boehm/gc}から 試してみるということだ。Boehm GCは conservativeかつincrementalかつgenerationalなGCで、しかも ネイティブスレッドが動いてい ても全スレッドのスタック領域をマークできるというかなりの優れものGCであ る。一度導入したとしてそのままBoehm GCをそのまま使い続けるのかどうか はわからないが、どちらにしてもなんらかの速度向上が期待できる方向に 進むだろう。

パーサ

仕様の点では、括弧を省略したメソッド呼び出しのネストが一律禁止になりそ うである。見てきたようにcommand_callは文法全域にかなりの影響を与えてい た。これが簡略化されればパーサもスキャナも随分すっきりするはずだ。 ただし括弧の省略自体がなくなることはありえない。

また実装面ではyaccを使い続けるかどうかでまだ思案中ということだ。使わ ないとすれば手書きで、ということだが、あれだけ複雑なものを手で実装でき るか、不安は残る。どちらを選んでも茨の道には違いない。

評価器

評価器は完全に作り直しとなる。目的は主に高速化と実装の簡略化であり、 主眼は二点だ。

まずrb_eval()の再帰呼び出しをなくす。なくす方法については「末尾再帰 →goto変換」のような感じ、と言うのが一番直感的だろうか。一つの rb_eval()の中でgotoを使い、ぐるぐる回るわけだ。するとまず関数呼び 出しが減るし、returnbreakのために使っていたsetjmp()も不要にな る。ただしCで定義されたメソッドの呼び出しが入れば嫌でも関数を呼ばざ るを得ないので、その区切りではやはりsetjmp()が必要だ。

バイトコード(byte code)というのはようするに機械語のプログラムみたい なものである。Smalltalk80の仮想マシンで有名になった用語で、命令がバイ ト単位で構成されているのでバイトコードと呼ばれる。上のレベルばかりいじっ ている人間からするとバイト単位なんてのはあたりまえに思えてしまうのだが、 機械語では命令がビット単位になっていることは多い。例えばAlphaだと命令 コード32ビットのうち先頭6ビットが命令種を表している。

バイトコード型にする利点は主に高速化である。理由は二つで、一つめは構文 木のようにポインタをたぐる必要がないこと。もう一つは局所的な最適化 (peephole optimization)がやりやすいことだ。

またバイトコードを保存しておいて読み込む場合はパースがなくなるのでそ こでも多少は速くなると考えられる。しかしパースはプログラムの開始時点に 一回しか行われない作業だし、元々パースにはあまり時間がかかっていない ので、そう大きな影響はない。

バイトコードの評価器がどんなふうになるか知りたければregex.cを見てみ るとよい。あとはPythonがバイトコードインタプリタだ。

スレッド

スレッドはネイティブスレッド対応。Rubyが誕生した1994年当時に比べると スレッドを取り巻く環境は格段に良くなっているし、ネイティブスレッドで いける、と判断されたのだろう。

ネイティブスレッドを使うということはCレベルでもプリエンプティブになる わけだからインタプリタ自体をマルチスレッドセーフにしなければならないが、 その点はとりあえずグローバルロックをかけて解決するようである。

それと知る人ぞ知る「継続」だが、どうもなくなりそうな気配だ。rubyの 継続はスレッドの実装に大きく依存しているので、スレッドがネイティブスレッ ドになれば継続も自然と消滅する。あれが付いているのは「実装できて しまった」からだし、ほとんど使われていないので問題ないだろう。

M17N

ついでにクラスライブラリについても少しだけ触れておこう。多言語化 (Multilingualization、略してM17N)についてだ。プログラミングに おいてM17Nとは何をすることか有体に言うと、複数の文字コードを扱えるよう にすることである。

似た話題では他に国際化(Internationalization、略してI18N)と いうのもあ る。こちらの例を挙げれば、エラーメッセージをユーザの好みの言語で出した り、日付の表現を国の慣習に合わせたりすることである。この例えでわかると おり、I18Nを実現するためにはM17Nの実現が必須である。しかし逆は成立しな い。

具体的にRubyを多言語化するためには何が必要か。一つにはパーサの対応、も う一つは文字列関係のライブラリ、具体的にはStringRegexpの対応、の 二つが必要である。

パーサの対応とは、コメントや文字列リテラル、正規表現リテラルに任意言語 (正確にはエンコーディング)を許すことだ。これが易しそうで難しい。 まず、rubyのパーサに エンコーディングを伝える方法が必要である。これまで見てきたよ うにRubyのプログラムは例外なくパーサを抜けたあとに評価される。つまりパー サにエンコーディングを伝えるのに通常の構文を使うことはできない。だから エンコーディングを指定するためになんらかの構文を追加する必要がある。

ライブラリでの対応はわりと簡単だ。現在あるmbclen()という仕組みを 素直に拡張したものになっている。

M17N対応rubyは既に実装されており、CVSレポジトリの ruby_m17nブランチから 取得可能だ。実装されたのに取り込まれていないのは仕様が成熟していな いと判断されたためである。いいインターフェイスさえ設計できれば1.9の途中 にでも入るのではないだろうか。

IO

現在のRubyのIOクラスは単純なstdioのラッパーなのだが、 このアプローチは

という二点で不満があった。そこでRiteではstdioを自前で持つ ことになりそうである。

Ruby Hacking Guide

ここまで我々は常にrubyを外から観察する者として行動してきた。だがもち ろんrubyは展示ケースに収められた製品とは違う。即ち我々の行動いかんに よってはこちらから影響を与えることができるのである。本書最後の節はコミュ ニティから提案されたrubyに対する働きかけについて話し、現在と未来の Ruby Hackerたちに対する餞とする。

世代別GC

まず、第5章でも触れた、木山真人さんによる世代別GC。 既に述べた通り現在のパッチだと

という点が問題なのだが、この場では初めての大型非公式パッチで あるという点を何より高評価したい。

鬼車

いまのRubyが使っている正規表現エンジンはGNU regexの改造版である。その GNU regexはもともとEmacsのために書かれたもので、それをマルチバイト対応 にしたものをさらにまつもとさんがPerl互換に改造した。という経緯から容易 に想像できるように、非常に複雑怪奇な構造になってしまっている。またこの GNU regexpのライセンスがLGPLであるためにrubyのライセンスが非常にやや こしくなっており、かねてからこのエンジンの置き換えが課題になってきた。

そこでいきなり登場したのが小迫清美さんの手による正規表現エンジン「鬼車」 である。これがかなり出来がよいらしく、すぐにでも本体に取りこまれそうだ。

鬼車はrubyのCVSレポジトリから以下のようにして入手できる。

% cvs -d :pserver:anonymous@cvs.ruby-lang.org:/src co oniguruma

ripper

続いて拙作のripper。parse.yを改造して拡張ライブラリにしたものだ。 ruby本体に対する変更というわけではないが、パーサのコンポーネント化の 一つの方向性としてここで紹介しておく。

ストリーム系のインターフェイスで実装しており、トークンのスキャンだの パーサでの還元だのをイベント形式で拾うことができる。添付CD-ROMに入れて おいた\footnote{ripper:添付CD-ROMのarchives/ripper-0.0.5.tar.gz}ので 使ってみてほしい。なお、このバージョンは半年ほど前のruby 1.7ベース なので今の文法とは少し違う。

これを作ったのはただ「アイデアを思い付いてしまった」というのが理由だっ たりするだが、そのわりにはうまくいったと思う。実装時間も三日くらいで実 にお手軽であった。

代替パーサ

まだ影も形もないプロダクトではあるが、rubyとは全く独立に使える RubyのパーサをC++で書いている人もいるようだ([ruby-talk:50497])。

JRuby

さらに過激に、インタプリタ全体を書き直してしまえー、 という動きもある。例えばJavaで書いたRuby 「JRuby\footnote{JRuby http://jruby.sourceforge.net}」 というものが登場している。Jan Arne Petersenさん以下、 かなりの大所帯で実装しているようだ。

ちょっといじってみた感想としては

ということは言えそうだ。ちなみに最後の「遅い」がどのくらいかと言うと、 オリジナルのrubyの20倍くらい(実行時間が)である。ここまで遅いとさす がに苦しい。やはりJava VMの上でRuby VMが動いているわけだから、遅くない はずがないのだ。マシンが20倍速になってくれるのを待つしかあるまい。

しかし全体としては想像よりずっとよくできている、という印象を受けた。

NETRuby

Javaで動くならC#でも動くだろう。というわけでC#で書いたRuby、 「NETRuby\footnote{NETRuby http://sourceforge.jp/projects/netruby/}」 というのが登場した。作者はartonさんである。

筆者の手元には.NET環境がないのでソースコードしか見ていないのだが、 本人の弁によると

というあたりが問題らしい。しかしinstance_evalは動くらしい(驚愕)。

rubyの開発に参加するには

rubyの開発者はあくまでまつもとゆきひろさん個人であり、最終的な rubyの方向については絶対的な権限がある。だが同時にrubyは オープンソースソフトウェアであり、誰でも開発に参加できる。参加できる、 というのは、意見を提案したりパッチを出したりできるということだ。 以下、具体的な参加方法について話す。

rubyの場合はメーリングリストを中心に開発が進んでいるので、各メーリン グリストに参加するのがよい。現在コミュニティの中心となっているメーリ ングリストは ruby-listruby-devruby-talkの三つである。ruby-listは 「Rubyに関係することならなんでもOK」のメーリングリストで、日本語である。 ruby-devは開発版rubyの話をするメーリングリストで、これも日本語であ る。ruby-talkは英語のメーリングリストだ。参加方法はRubyの 公式サイト\footnote{Rubyの公式サイト:http://www.ruby-lang.org/ja/} の「メーリングリスト」のページに載っている。これらのメーリングリストは どれも読むだけのメンバーも歓迎なので、とりあえずしばらく参加してみて 議論を眺め、雰囲気を捕むといいのではないだろうか。

日本から活動が始まったRubyだが、最近は「主導権はruby-talkに移った」 と言われてしまったりすることもある。 だが開発の中心が相変わらずruby-devであることに変わりはない。なにしろ rubyのコミット権を持っている人間(即ちコアメンバー)はほとんど日本語 ユーザなのでわざわざ英語で話すのも面倒だし、自然とruby-devに足が向い てしまう。将来英語を使うコアメンバーが増えてくれば状況も変わるかもしれ ないが、当分の間はruby開発のコアはruby-devだろう。

ただ日本語が使えないと開発に参加できないというのも困るので、今は ruby-devの要約を一週間に一度英訳してruby-talkに流すようになってい る。筆者もその要約に参加しているのだが、現在は三人の持ち回りで やっているため非常に厳しい。要約を手伝ってくれるメンバーは常時 募集中である。我こそはと思うかたは是非ruby-listで参加表明して いただきたい。

そして最後に、ソフトウェアはソースコードがあればいいというものでもない。 各種ドキュメントやウェブサイトの整備も必要である。そしてそういうことを してくれる人は常に不足ぎみだ。 ドキュメント関連の活動にもいちおう専用メーリングリストがあるが、とりあえ ずはruby-listで「何かやりたい」と言ってくれればいい。筆者もできるだ け答えるようにするし、他のメンバーも反応してくれるだろう。

最後に

さて、長かった本書もこれで終わりだ。ページ数との兼ね合いもあるのであら ゆる部分を懇切丁寧にというわけにはいかなかったが、rubyの根幹について は全て語り尽くした。これ以上ウダウダと付け加えるのはよそう。まだわから ないことがあれば納得するまで自分でソースコードを読んで確かめてほしい。


御意見・御感想・誤殖の指摘などは 青木峰郎 <aamine@loveruby.net> までお願いします。

『Rubyソースコード完全解説』 はインプレスダイレクトで御予約・御購入いただけます (書籍紹介ページへ飛びます)。

Copyright (c) 2002-2004 Minero Aoki, All rights reserved.