ruby
は「完成してしまったソフトウェア」ではない。まだまだ発展途上で
あり、多くの課題を抱えている。まずは現在のインタプリタに内在する
問題を摘出してみよう。話題の順番はだいたい本書の章の順番に沿っている。
現在の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プログラムの解析が必要になる例としては
indent
やlint
のようなツールが
挙げられるが、こういうものを作るのにも非常に苦労する。リファクタリング
ツールのような複雑なものになるともはや絶望的である。
ではどうしようか。同じものを作り直すのが無理なら、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
の動向を示す。
現時点でのrubyの最新バージョンは安定版が1.6.7、開発版が1.7.3だ。 だがそう遠くないうちに次の安定版1.8が出そうである。そうすると同時に 開発版の1.9.0がスタートする。そしてその次はちょっと変則的に1.9.1が 安定版となる。
安定版 | 開発版 | 開始時期 | |||
1.6.x | 1.7.x | 2000-09-19に1.6.0リリース | |||
1.8.x | 1.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の実装では、
まず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()
のような再帰呼び出しをなくす
まずrb_eval()
の再帰呼び出しをなくす。なくす方法については「末尾再帰
→goto
変換」のような感じ、と言うのが一番直感的だろうか。一つの
rb_eval()
の中でgoto
を使い、ぐるぐる回るわけだ。するとまず関数呼び
出しが減るし、return
やbreak
のために使っていたsetjmp()
も不要にな
る。ただしCで定義されたメソッドの呼び出しが入れば嫌でも関数を呼ばざ
るを得ないので、その区切りではやはりsetjmp()
が必要だ。
バイトコード(byte code)というのはようするに機械語のプログラムみたい なものである。Smalltalk80の仮想マシンで有名になった用語で、命令がバイ ト単位で構成されているのでバイトコードと呼ばれる。上のレベルばかりいじっ ている人間からするとバイト単位なんてのはあたりまえに思えてしまうのだが、 機械語では命令がビット単位になっていることは多い。例えばAlphaだと命令 コード32ビットのうち先頭6ビットが命令種を表している。
バイトコード型にする利点は主に高速化である。理由は二つで、一つめは構文 木のようにポインタをたぐる必要がないこと。もう一つは局所的な最適化 (peephole optimization)がやりやすいことだ。
またバイトコードを保存しておいて読み込む場合はパースがなくなるのでそ こでも多少は速くなると考えられる。しかしパースはプログラムの開始時点に 一回しか行われない作業だし、元々パースにはあまり時間がかかっていない ので、そう大きな影響はない。
バイトコードの評価器がどんなふうになるか知りたければregex.c
を見てみ
るとよい。あとはPythonがバイトコードインタプリタだ。
スレッドはネイティブスレッド対応。Rubyが誕生した1994年当時に比べると スレッドを取り巻く環境は格段に良くなっているし、ネイティブスレッドで いける、と判断されたのだろう。
ネイティブスレッドを使うということはCレベルでもプリエンプティブになる わけだからインタプリタ自体をマルチスレッドセーフにしなければならないが、 その点はとりあえずグローバルロックをかけて解決するようである。
それと知る人ぞ知る「継続」だが、どうもなくなりそうな気配だ。ruby
の
継続はスレッドの実装に大きく依存しているので、スレッドがネイティブスレッ
ドになれば継続も自然と消滅する。あれが付いているのは「実装できて
しまった」からだし、ほとんど使われていないので問題ないだろう。
ついでにクラスライブラリについても少しだけ触れておこう。多言語化 (Multilingualization、略してM17N)についてだ。プログラミングに おいてM17Nとは何をすることか有体に言うと、複数の文字コードを扱えるよう にすることである。
似た話題では他に国際化(Internationalization、略してI18N)と いうのもあ る。こちらの例を挙げれば、エラーメッセージをユーザの好みの言語で出した り、日付の表現を国の慣習に合わせたりすることである。この例えでわかると おり、I18Nを実現するためにはM17Nの実現が必須である。しかし逆は成立しな い。
具体的にRubyを多言語化するためには何が必要か。一つにはパーサの対応、も
う一つは文字列関係のライブラリ、具体的にはString
とRegexp
の対応、の
二つが必要である。
パーサの対応とは、コメントや文字列リテラル、正規表現リテラルに任意言語
(正確にはエンコーディング)を許すことだ。これが易しそうで難しい。
まず、ruby
のパーサに
エンコーディングを伝える方法が必要である。これまで見てきたよ
うにRubyのプログラムは例外なくパーサを抜けたあとに評価される。つまりパー
サにエンコーディングを伝えるのに通常の構文を使うことはできない。だから
エンコーディングを指定するためになんらかの構文を追加する必要がある。
ライブラリでの対応はわりと簡単だ。現在あるmbclen()
という仕組みを
素直に拡張したものになっている。
M17N対応ruby
は既に実装されており、CVSレポジトリの
ruby_m17n
ブランチから
取得可能だ。実装されたのに取り込まれていないのは仕様が成熟していな
いと判断されたためである。いいインターフェイスさえ設計できれば1.9の途中
にでも入るのではないだろうか。
現在のRubyのIO
クラスは単純なstdio
のラッパーなのだが、
このアプローチは
という二点で不満があった。そこでRiteではstdio
を自前で持つ
ことになりそうである。
ここまで我々は常にruby
を外から観察する者として行動してきた。だがもち
ろんruby
は展示ケースに収められた製品とは違う。即ち我々の行動いかんに
よってはこちらから影響を与えることができるのである。本書最後の節はコミュ
ニティから提案されたruby
に対する働きかけについて話し、現在と未来の
Ruby Hackerたちに対する餞とする。
まず、第5章でも触れた、木山真人さんによる世代別GC。 既に述べた通り現在のパッチだと
ruby
に合わせてアップデートが必要という点が問題なのだが、この場では初めての大型非公式パッチで あるという点を何より高評価したい。
いまの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。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]
)。
さらに過激に、インタプリタ全体を書き直してしまえー、
という動きもある。例えばJavaで書いたRuby
「JRuby\footnote{JRuby http://jruby.sourceforge.net
}」
というものが登場している。Jan Arne Petersenさん以下、
かなりの大所帯で実装しているようだ。
ちょっといじってみた感想としては
instance_eval
が効かないようだ(これは仕方ないか)
ということは言えそうだ。ちなみに最後の「遅い」がどのくらいかと言うと、
オリジナルのruby
の20倍くらい(実行時間が)である。ここまで遅いとさす
がに苦しい。やはりJava VMの上でRuby VMが動いているわけだから、遅くない
はずがないのだ。マシンが20倍速になってくれるのを待つしかあるまい。
しかし全体としては想像よりずっとよくできている、という印象を受けた。
Javaで動くならC#でも動くだろう。というわけでC#で書いたRuby、
「NETRuby\footnote{NETRuby http://sourceforge.jp/projects/netruby/
}」
というのが登場した。作者はartonさんである。
筆者の手元には.NET環境がないのでソースコードしか見ていないのだが、 本人の弁によると
というあたりが問題らしい。しかしinstance_eval
は動くらしい(驚愕)。
ruby
の開発に参加するには
ruby
の開発者はあくまでまつもとゆきひろさん個人であり、最終的な
ruby
の方向については絶対的な権限がある。だが同時にruby
は
オープンソースソフトウェアであり、誰でも開発に参加できる。参加できる、
というのは、意見を提案したりパッチを出したりできるということだ。
以下、具体的な参加方法について話す。
ruby
の場合はメーリングリストを中心に開発が進んでいるので、各メーリン
グリストに参加するのがよい。現在コミュニティの中心となっているメーリ
ングリストは
ruby-list
、ruby-dev
、ruby-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.