MRI の開発文化について紹介します。MRI: Matz Ruby Interpreter (要するに ruby コマンド)はオープンソースとして、1993 年から開発が行われてきました(実際に、ソースコードが公開されたのは 1995 年 12 月です)。オープンソースソフトウェアなので、誰でも開発に参加して良い、ということになっていますが、何も知らないと参加は困難です。そこで、本稿ではまず、MRI の開発がどのように行われているか、笹田が把握している範囲で紹介します。
本稿で扱う内容:
- どのように MRI は開発されているか(開発フローや利用しているツールの紹介)
- (バグなどの)チケットはどのように管理されているか
- どのように MRI の中身についての情報を知るのか、それから開発コミュニティの情報を得るのか
- MRI には、どのような未解決問題が残っているのか
Ruby はオープンソースで開発されているため、誰でも開発に参加できます。この文章を読んでいる皆さんを歓迎します!
Ruby の開発と一言で言っても、次の二つを指します。
- (1) プログラミング言語 Ruby の仕様策定
- (2) MRI という Ruby インタプリタの実装
プログラミング言語は、なんとなくずっと変らない、という印象があるかもしれませんが、結構変ります。具体的には、(a) 新機能の追加、(b) イマイチだった仕様の変更の 2 点になります。
我々は、Ruby をさらに魅力的にするために、この仕様変更を行っています。例えば、もっと便利に、もっと書きやすく、といった観点から Ruby 言語に変更を加えています(ちなみに、言語処理系の高速化のために言語を変更(多くの場合、言語の制限の追加)を行うことも、たまにありますが、Ruby には「速度よりも書きやすさ」というポリシーがあるため、あまり推奨されません)。
ただ、プログラミング言語利用者としては、今まで習得した内容が変っていくことになるので、負担になります。(a) 新機能は、新しく覚えることが増えるのは、まだマシかもしれません。(b) では、覚えたことを、覚え直さねばならないからです。さらに、複数の Ruby のバージョンで動作するプログラムを書かないとならない人にとっては、バージョン間の違いを適切に把握する必要があります。
例えば、Ruby 1.8 以前では、?x
という記法は、ASCII コードの 0x78 という数値を返していました。しかし、Ruby 1.9 からは、複数エンコーディング対応のため、"x"
という1文字の文字列が返ることになりました(各エンコーディングでのコードポイントの数値を返す、という案もありますが、その場合エンコーディング情報が失われてしまう、という問題があります)。これまで、?x
は数値が返ると思っていた人にとっては、覚え直しが発生しましたし、それまで ?x
を使っていた箇所をすべて書き換えるという作業が発生しました。仕様としては整理され、良いものになりましたが(だから変えたんですが)、互換性という観点から、多くの人に負担をかけることになりました。
「よりよいものを目指す」という観点と、「過去との互換性を保つ」という観点をなるべく両立させられるように、互換性への影響はなるべく少なくなるように、新しい仕様を検討しています。
もし、必要があって互換性を壊す変更が必要な場合は、(影響範囲にもよりますが)互換性が崩れるという警告を出す、マイグレーションパス(このように変更すれば良い、という手順)を設ける、といったことを気を付けて行っています。
互換性に影響しない変更というと、これまで動かなかったコードを動くようにする、という修正があります。動かなかったコードを書く人は(多分)居ないからです。例えば、Ruby 2.6 で導入される a..
といった endless-range は、これまでは文法エラーで記述できなかったので、誰も書いたことがない、ということが期待できるため、さっと入りました(が、実はやはり互換性に絡む問題が隠れていて、議論になりました [Misc #14769])。
変更は、MRI のバージョン更新のタイミングで導入されます。Ruby のバージョンは x.y.z (例えば、Ruby 2.5.1 など)で表されます。それぞれ、x: メジャー、y: マイナー、z: ティーニーと呼びます。
- ティーニーの変更は、バグ修正のみであり、ティーニーの変更では、アプリケーションの互換性が崩れることが無いように気を付けています。
- マイナーの更新時は、(なるべく互換性を崩さない)機能の修正、追加が行われます。
- メジャーの更新時は、結構大きな変更があってもしょうがない、かもしれません。
なお、このあたりの判断と努力は、個々の開発者によって行われており、何か保障があるわけではありません。
MRI は Ruby 言語のリファレンス実装、つまり「Ruby 言語の仕様を満たす言語処理系」として扱われているため、採用が決定された Ruby の仕様は、MRI へ搭載されます。
リファレンス実装であるため、仕様の提案が行われるときは、MRI での実装(パッチ)があると、議論されやすいです。単なる提案や要望は、後回しにされがちです。仕様を検討しているとき、とりあえず MRI 上で実装して試す、といったこともよく行われます。MRI に入っていない、「Ruby 言語の仕様」というものは、おそらくありません。
MRI のバグが修正されたとMRI の仕様が変わった場合は、Ruby の仕様が変更された、ということなります。そのため、バグ修正においても、互換性が無くなることに対する検討が慎重に行われています(多分)。
ただ、MRI 特有の変更も多いため、MRI の変更 == Ruby 言語の変更、ということではありません。例えば、性能向上のための変更などがそれにあたります。また、MRI の GC やバイトコード特有の操作を行うメソッドなどは、Ruby 言語ではなく、MRI 特有の仕様と考えられます。例えば、GC.disable
というメソッドは、GC を起こさないようにする、というものですが、JVM 上で作っていて GC をいじれない場合は、利用することはできません。
Ruby のプライマリリポジトリは Git で管理されています https://www.ruby-lang.org/ja/documentation/repository-guide/。ある人々がこのリポジトリを修正することができ、その人々のことを「コミッタ」と呼んでいます。現在、全世界で100人程度のコミッタがいらっしゃいます(ただし、現在アクティブに活動している人数は、もっと少ないです。過去にコミッタになると、(今のところ)コミッタを抜けることはできません。怖いですね)。
コミッタは Ruby の全ソースコードを修正することができますが、(なんとなく)担当範囲が決まっているため、担当範囲外の修正を行う場合は、担当のコミッタの意見を尊重することが求められています。例えば、笹田は現在 Ruby で利用している VM の開発者なので、VM に大きな変更を加える場合は笹田に修正を相談して欲しい、と思っています(現実的には、されないことも多いですが)。
コミッタ間でのコードレビュー体制はなく、コミットされた修正を眺めて気づいたら指摘したり、問題(バグ報告等)があったときに bisect する、といった体制で開発は行われています。大きな修正では、コミッタ間でレビューを求め合ったりします。
なお、https://github.com/ruby/ruby/ という GitHub のリポジトリにミラーがあります。
仕様変更、バグ修正など、すべての議論は Redmine https://bugs.ruby-lang.org/issues/ にチケットとしてまとめられます(されるべきです)。チケットの登録やコメントなどは、メーリングリストに配信されます。メーリングリストには日本語向けの ruby-dev と、英語向けの ruby-core があります https://www.ruby-lang.org/ja/community/mailing-lists/。
仕様変更といった大事なチケットは、英語で起票し、世界中に周知し、広く議論することを強く推奨されています。軽微な修正は、日本語でも受け付けています。日本語で議論を始めたけれど、「仕様の議論だから英語のチケットにしよう」といったことも行われています。
チケットには、大きく分けて、「機能追加要求(Feature request)」と「バグ報告(Bug report)」があります。
- Feature requests
- Ruby 仕様の追加や修正(Ruby 言語への要求だったり、MRI への要求だったり)。
- ところで、Redmine の URL は
bugs
で始まってますね。これは、仕様変更のための修正(いや、どんな修正にも、かな?)にはバグが混入するだろうから、bugs
でいいんだとか。
- Bug reports
- おかしな挙動や、性能の問題といった、仕様変更以外のすべてが含まれます。
Ruby が利用している Redmine では、チケットを登録する際、チケットに記述する言語を英語か日本語かでドロップダウンメニューから選びます。選択した言語によって、チケットの内容を配信するメーリングリスト(ruby-core か ruby-dev)が決定されます。
良いバグ報告には、次のような内容が含まれていることが期待されます。
- Summary(問題の短いまとめ)
- 再現コードと再現環境(ruby -v の結果(必須です)、OS、コンパイラなどのバージョン、その他)
- 期待する挙動
- 実際に得られた挙動
- (可能なら)その問題を修正するためのパッチ
詳細は、英語 https://github.com/ruby/ruby/wiki/How-To-Report (English) もしくは日本語 https://github.com/ruby/ruby/wiki/How-To-Report-Ja のドキュメントがありますのでご参照ください。
良い Feature request (機能追加要求)には、次のような内容が含まれていることが期待されます。
- Abstract(提案の短いまとめ)
- Background(背景:現在、何が問題なのか、実際に困っているのは何であるか、実際のユースケースは何か)
- Proposal(提案)
- Implementation (実装:実装があれば、その提案が実現可能であるかを判断する強い証拠になります)
- Evaluation(評価:提案によって、何がどのように良くなったのか、実装があれば、その性能は十分であるか、など)
- Discussion (議論:検討するべき内容、他のアプローチとの比較など)
- Summary(まとめ)
機能追加要求では「実際にどんなユースケースがあるのか」という点が(最近だととくに)よく求められます。例えば、「使わないけど、整合性のためにはこういう機能があるほうがいいのではないか」という提案は、あまり通らないことが多いです(この場合、整合性よりも互換性のほうが優先されます)。
さらなる詳細は https://github.com/ruby/ruby/wiki/How-To-Request-Features (英語)をご参照ください。
GitHub への issue および Pull request は運が良ければ対応されますが、運が悪ければ放置されます。それらを作成した場合、Redmine への issue を別途作成し、Github の当該 URL を示す、というのが良いと思います(もしくは、誰かコミッタに連絡すれば、対応してくれるかもしれません)。
MRI は大きく複雑なソフトウェアなので、テストによる品質保証が必要です。そのため、約 5,000ファイル、45万行程度のテスト用スクリプトが存在します(その話は次の資料で)。
また、プログラムが大きいだけで無く、様々な環境で Ruby を動作させるので、テスト実行環境も沢山用意する必要があります。 例えば OS だけでも、Linux、Mac OSX、Windows が有名ですが、それ以外にも、*BSD、Solaris など、色々あります(今はそんなにないのかな?)。 ハードウェア/CPU も、多くの場合、Intel x86/64 CPU、もしくは ARM だと思いますが、それ以外のプロセッサを利用していることがあります。 なお、Ruby がサポートするプラットフォーム一覧はこちら:https://bugs.ruby-lang.org/projects/ruby-trunk/wiki/SupportedPlatforms。
このように、MRI は様々な場所で利用されていますので、いろいろな組み合わせでテストすることが望ましいです。 テストを自動化するためには、CI を用いることが一般的になっています。我々も、CI 環境を用意しています。
Ruby では、有名どころである Travis-CI も用いていますが、さまざまな環境で幅広いテストを行うために http://rubyci.org という、いろいろな環境でテストした結果をまとめるサービスを作って利用しています。
通常、CI は自分が管理する計算リソースを利用してテスト等を行いますが、我々は多くの環境を持ってはいません(最近は AWS を利用しているので、管理していると言えなくもないですが)。 自分たちですべての計算機リソースを用意する代わりに、計算機リソースを持っている人に定期的にテストを実行してもらい、その結果を収集する、という方法にしています。 テストに利用するのは chkbuild というフレームワークで、Ruby のビルド・テスト実行を行い、結果を生成します。 テスト結果は、前回の結果の diff として表示できるため、どのバージョンでバグがあったか見ることができます。
chkbuild は良いテストフレームワークなのですが、いくつかの理由(例えば、毎回ソースコードのダウンロードから始める)から、実行に時間がかかります(たいてい、数十分)。 そこで http://ci.rvm.jp/ という環境を用意し、簡易ですが高速にテストを回していく環境を用意しています。 前回のビルド結果を(可能なら)再利用したり、並列ビルド・テストを利用する、といった仕組みで、短いもので 2、3分で結果が出ます。例えば、コミットにミスがあったとき、(ci.rvm.jp を監視していれば)すぐに間違いに気づくことができます。 また、2、3 分で済むテストを繰り返しているため、一日に数百回のテストを行うことで、「時々しか出ないバグ」を(時々運良く)発現させることができます。
コミッタは、基本的には手元でテストを通してからコミットすることが求められます。 うっかりテストの通っていないコミットをしてしまった時は、まず http://ci.rvm.jp/ でエラーが検出されます(コミッタの集まる slack にアラートが流れます)。 また、手元の環境(例えば Linux)では動いているが、別の環境(例えば Mac OSX)では動かない、ということもよくあります。その場合、http://rubyci.org を見ることでほかの環境の実行結果を確認することができます。
Ruby / MRI には、まだまだ手を付けたい問題がたくさんあります。下記にいくつかあげておきます。
- 仕様の議論
- Ruby 2.x (2.6, ...)
- Ruby 3
- JIT compilation (only for performance? drop backward compatibility?)
- Static checking
- Concurrent execution
- 性能改善
- ベンチマークの整備
- 性能改善
- ドキュメンテーション
- バグ修正
最近笹田がなんとかしたいと思っている問題(インターナルが多い)もあげておきます。
- バイトコードシリアライザの性能・品質向上
- メソッド呼び出しの仕組み変更による高速化
- コードのインライン化の対応
- 世代別 GC 対応オブジェクトを増やして性能向上
Time
オブジェクト- これに関してのサーベイ
- 世間の gem を Ruby の CI でテストする仕組みの用意
- 同じく、ベンチマークする仕組み
参考文献 をご参照ください。 また、深くハックする場合は、C 言語の知識が必要になります。
- Ruby's redmine: https://bugs.ruby-lang.org/projects/ruby/
- Ticket
- Wiki
- Mailing list
- https://www.ruby-lang.org/en/community/mailing-lists/ (En) https://www.ruby-lang.org/ja/community/mailing-lists/ (Ja)
- ruby-core (English)
- ruby-dev (Japanese)
- Conference, meetup
- RubyConf and other international conferences(ほぼ、英語で議論しています)
- 日本国内
- RubyKaigi
- RegionalRubyKaigi
- Asakusa.rb, *.rb
- Ruby 開発者会議
- 毎月、東京のどこかで行っています。
- 個人へのコンタクト
- Twitter
- @yukihiro_matz
- ...
- Twitter
- Gitter https://gitter.im/ruby/ruby
- これを機に作りました。
本稿では、いくつか面倒そうなルール的なことを書きましたが、我々 Ruby インタプリタ開発者が最も重視しているのは「ハッキング」です。 もし、偉大なパッチを寄贈してくださるのであれば、多少ルールからそれても、全力でサポートします(もしくは、全力で議論します)。
コードを書きましょう。