STORES Product Blog

こだわりを持ったお商売を支える「STORES」のテクノロジー部門のメンバーによるブログです。

EuRuKo2024 で発表してきました(YARVの話)

テクノロジー部門の笹田です。寒暖差が大きく、体調が心配になる季節ですね。うちの家族は私以外が風邪ひいてしまい、いつ私にうつるか戦々恐々しています。皆様もどうぞご自愛ください。

先月 9/11-13 に Sarajevo, Bosnia & Herzegovina で開催された EuRuKo2024 でキーノートの発表をしてきたので、イベントと発表した内容について簡単にレポートします。

EuRuKo2024

EuRuKo は、ヨーロッパで行われる代表的な Ruby に関するカンファレンスです。場所と運営者を毎年交代していく(多分)珍しいカンファレンスで、これまで様々な場所で行われてきました。私は今回4回目の参加で、プラハ、ギリシャ、ザルツブルグ、そしてサラエボに参加しましたが、どれも大変良かったです(ヨーロッパはたいていどこも良いと感じるのかもしれない)。

通常のトーク(34、3パラレルセッション)、キーノートが7本、パネルセッションが2本、ワークショップが5本と、豪華な会議でした。

会議では久々に Dave Thomas が居て、相変わらずいい話をしていました。Elixir の Jose も居たのでお二人にプログラミングElixir(第2版) | Ohmsha をお渡ししました。Jose の発表も、とてもなれた感じで Livebook の話を滔々とされていました。

Dave Thomas のキーノート

Matz は Ruby 4 は Namespace と Annotation が入ったら出してもいいかも、とか言ってました。

会全体で、コミュニケーションデザインというか、人々を交流させるための工夫がありました。専用の Discord や、トレーディングカードを用意して話のタネにしてくれ、といっていたり、立派な懇親会に DJ が居たり、といった感じです。でも、ランチは結構おたかめ(20 EURくらいだっけ)。知らない人といろいろ喋ったのは朝食やランチだった気がします(先輩パパから思春期の娘の話とか聞いてた)。知らない人とちゃんと交流しようって意識が高い人が多くてよいですね。いや、そういう人だから交流してもらえたのか。

司会の方が「良かったらスタンディングオベーションをぜひ」と煽っていたため、結構立って拍手していた方がいらっしゃいました。個人的には同調圧力を感じて結構苦手。

次の年の場所は、毎回会期中の投票で決まるのですが、EuRuKo2025 はポルトガルの Viana do Castelo に決まりました。

スペインのバルセロナ、そしてサラエボをもう1回、という3者に対して投票が行われて、見事 Viana do Castelo が選ばれました。行ったことがなかったので、個人的にはバルセロナがよかったんですが、ヨーロッパの人は行ったことがある人が多そうだからなんですかね(オーバーツーリズムで大変という声が聞こえました)。

旅程

サラエボの街

開催場所のホテルは、いわゆる観光地である旧市街からソコソコ離れていました(車で1時間弱。ちなみに、当初は旧市街の場所を考えていたが、都合によりできなかったとのこと)。開催前日の朝に現地に到着したので、お土産でも買おうかと EuRuKo 用 Discord で「お土産を買うならどの駅で降りるといいですか」(路面電車が会場近くから旧市街につながっていた)と投稿したところ、街を案内してやるぜ、って方をご紹介していただいて、車で連れてっていただきました。

Sehercehaja Bridge のあたり

サラエボというと1990年代の紛争を強く思い出すのですが、やはりその影響は色々なところで見られました。旧市街は戦火を逃れた建物や新しい建物が結構混ざっていたり、といった具合です。また、新しいビル群がたっていたりして、「この辺は外国からの投資がたくさん入ってる」といったことを教えていただきました。

そしてお土産物屋の並びは日本の観光地と一緒だなぁ、と思ったり。案内いただいて、自分では入れないであろうローカルレストランで名物というチェヴァプチチも頂けました。案内してくださった方が博識で、色々な歴史とともに教えてくださいました、が英語と歴史の教養がない私にはちょっとなかなか...(数時間案内してもらったのにゴメンな...)。

EuRuKo というかヨーロッパに来ると、結構案内してくださる方がいて(プラハはMatz と一緒だったからかもしれない)、その親切に感謝しかありません。一生の思い出です(中身はあんまり覚えてないんだけど...)。海外からゲストを呼ぶときは、希望される方がいたら地域を案内されると良いかもしれません。しかし、案内してくださる方々の博識なこと。私は生まれてからずっと東京ですが、東京のことそんなに知らないぞ。

会議場のホテルの隣にモールがあって、雑に買い物するために何回か行きました。海外のスーパー巡るのが好きなんです。小麦が日本で売ってる米のように積んであって流石、とか。

つんであった小麦(通貨はKM、1KM は約 83円(執筆時))

イスタンブール

一番安い航空券を探して、トルコ航空でイスタンブール経由で行くことにしました。初めて利用する航空会社だったのですが、だいたい快適に過ごせました。機内食も美味しかった。トルコ航空ではStopover opportunity in İstanbul | Turkish Airlines ® というのがあって、乗り継ぎに時間がかかるとホテル一泊分を出してくれたり、ツアーを利用できたりするという制度があるということで、家族の了解を得て一泊だけしてきました。観光地を少し巡っただけですが、とてもよかったです。ご飯は適当に入ったので、もう少しちゃんと調べていけばよかった気がする。

イスタンブールなので、飛行機は黒海のあたりを通るのですが、「なるほどここがニュースなどで見るところか」とひとり喜んでいました。

Keynote: 20th years of YARV

年の初めに「喋んない?」と聞かれて、国外行くの大変だなぁ、と思いつつウジウジとしていたら、結構メールで催促してくださったので、最終的に行くことにしました。趣旨として、Ruby 関係のいろんな人を呼びたい、ということだったみたい。招待なので航空券と宿泊費は向こう持ち。ありがたい。

話した内容は、YARV 開発開始から20年だったので、それをテーマに、これまでの開発と後悔していること、それから今後どうしていこうかな、という内容を盛り込みました(スライド: https://www.atdot.net/~ko1/activities/2024_euruko.pdf)。

日本語であまり触れたことがない話だったので、内容を少しご紹介します。

YARV の概略

YARVを作り始めたときの話

Ruby プログラムを高速に実行することを目的とする新しい仮想マシン(VM)YARV: Yet Another RubyVM の開発は、2004 年のお正月(元日)に始めました。当時私は博士課程 1 年生でしたが、正月休みで時間があったことと、そのタイミングまでで色々と実現方法を考えており、実際に手を動かしてみようと思ったからでした。メールを漁ってみると、1/6 に簡単なプロトタイプが動いていることがわかります。

ちなみに、この辺の最初期に YARV をどう作っていたかは Ruby Under a Microscope の付録として寄稿したものがあります (Koichi Sasada Encourages Us To Contribute To Ruby - Pat Shaughnessy )。

YARV 年表

開発を進めて、まつもとさんの OK をもらい、2008 年 12 月に Ruby 1.9.1 が production release として YARV が入った Ruby としてリリースされました(Ruby 1.9.0 は、いろいろな理由で「開発版」としてリリースされたのでした)。以来、JIT が入ったり、協力してくださる方が増えて色々強化されたりしましたが、大きな枠組みは変わらず Ruby 3.3 まで ruby コマンドは YARV ベースで動いています。多分 3.4 も変わらないと思います。

YARV には3つの特徴があります。

1つ目は YARV が簡単なスタックマシンである、ということです。Ruby 1.8 では、Ruby プログラムを受け取って抽象構文木(AST)を作ったあと、その AST を辿りながら実行する、という実行モデルですが、YARV ではこの AST からさらにスタックマシンの命令列(いわゆるバイトコード)を生成して、その命令列を順に実行していく、というものになります。抽象構文木を素直に辿るモデルに比べ、いろいろな最適化がやりやすいという特徴を持っています(細かい話は YARV Maniacs 【第 1 回】 『Ruby ソースコード完全解説』不完全解説 の連載とかに少しあります)。

2つ目は自動生成を多用して色々省力化を行っているというものです。命令の定義ファイルから、命令を実行するための C のコードや最適化のためのコード、VM のメタデータ(どんな命令を持っているかなどのデータ)、逆アセンブラ、シリアライザ、それからドキュメントなどを生成します(今はドキュメントは全然使われてないけど...)。この手のデータを破綻なく管理するのはちょっと面倒ですが、機械的に決まるものは一つの記述から自動生成することで一貫性を保ちつつ利用することができます。この自動生成の仕組みは、VM の命令などを気軽に追加したり変更できるようにするための工夫となっています。

3つ目は高速化のための工夫、とくに Ruby に適したものです。いろいろやっていて、VM の命令を工夫したりするのが目立つのですが、もっとも大きな貢献(発見)はブロック付きメソッド呼び出しの高速化だと思っています(あまり言ったことも言われたことものないのですが)。

ブロック付きメソッド呼び出しの高速化

具体的には、ローカル変数環境のエスケープを必要になるまで遅延させる、というテクニックになります。iter{ block } としたとき、ナイーブに作ると block が Proc としてそのスコープよりも長い寿命を持つ「可能性がある」(つまり、iter が終わっても Proc として block が残る可能性がある)ため、block から参照可能なローカル変数(など)をヒープにコピーする(退避する)必要があります。とくに、Ruby には eval があるため、どのローカル変数をコピーすればいいか、事前に知ることができません(そこがわかっていると、ラムダリフティングといった既知の最適化が利用できます)。YARV では、これを本当に必要になるとき、つまり Proc 化するまでこの退避を遅延します。これによって、Ruby で多用されるブロックつきメソッド呼び出しがかなり速くなっています。

YARV が実現したよかったこと、イマイチだったこと

さて、YARV が実現した良かったことをご紹介します。

よかった成果:スタックマシンでRubyをどう表現するかを明らかにしたこと

まず、Ruby を VM、とくにスタックマシンで表現するにはどうすればよいか、YARV 以前ではまだ明らかになっていませんでした。実際に Ruby の仕様を十分に満たす VM を実現した、というのが一つの成果になります(もしくは、VM にとってやりづらい仕様を消しちゃったりとかも含みます)。この成果はさらなる高速化を容易にしたり、さらに VM の表現を利用する JIT などでも活用されています。

イマイチな点:きちんと定義されていない命令

イマイチな点は、その仕様がカッチリと決まっていないということです。JVM などは VM の命令をきちんと仕様書としてまとめており、命令レベルの互換性を重視するようになっています。そして、それを利用するツールがその命令を安心して使えますが、YARV は命令の定義がしっかり固まっていないため、ある Ruby のバージョンの命令を利用、といったことしかできません。これは、そもそも YARV が自動生成系などの工夫で変更に対して柔軟に作られている、という特徴の裏返しでもあります。そんな中で YJIT などができているのですが、凄いことだと思います。

(スライドでは JIT に関する考察やなぜ VM の性能が今でも重要か、などがありますがこの記事では省略)

良かった点:メソッド呼び出しの高速化

他のよかった成果として、メソッド呼び出しの高速化があります。先ほどはブロック付きメソッド呼び出しについてご紹介しましたが、Ruby にはとにかくメソッド呼び出しの機会が多いので、たくさんの高速化の努力が詰まっています。

ただ、イマイチな点として、メソッドのインライン化のような、まだできそうなことをやれていない、というところがあります。

良かった点:特化命令の導入

メソッド呼び出しを手軽に高速化する手法として、特化命令の導入というのがあります。よく呼ばれる軽いメソッドを実行する専用の命令を導入するというものです。例えば String#empty? は、実際に必要な処理はほんのちょっとで、それに比べてメソッド呼び出しにかかる時間が十分長いため「軽いメソッド」になります。そして、多分よく利用されるメソッドだろうということで特化命令として導入しています。

どれくらい手軽に導入できるかというと、YARV の最初の公開から1週間くらいでフィボナッチ数を計算するプログラムが3倍くらい高速化するくらい手軽な高速化です(多分、だれでも思いつくし、実装も簡単)。

イマイチな点:拡張性を欠いた特化命令

ただし、特化命令の仕組みは、特定のメソッドのみを高速化するため、「一般的によく使う」軽量なメソッドをてきとうに選んで実装する、ということになります。もちろんアプリケーションによって使うメソッドは違うわけで、この特化命令が有効だったりそうでなかったりすることがあるわけです(仕組み上、例えば UserDefinedClass#empty? が呼ばれると、普通の呼び出しに比べてちょっと遅くなったりします)。

その辺、アプリケーションによって自由に特化命令のようなオーバヘッドを削減する処理を入れる仕組みを導入できればよかったのに、という後悔があります。いくらか設計まではしたのですが、面倒で導入まで至りませんでした。

良かったり悪かったりする点:ブロックの管理

ブロック付きメソッド呼び出しの話はしましたが、それ以外にもブロックを受け取ったりするときに Proc 化を遅延する Lazy Proc creation なんかをして、とくに delegation 的なメソッド呼び出しの高速化に寄与しています。

が、そもそもブロックを呼び出すときの高速化が、メソッドを呼び出すときの高速化の努力に比べてなされていません。ブロックの呼び出しって、隠れた仕様がむっちゃくちゃあって、ちゃんと取り扱うのが大変な部類の機能なのですが、でも、ブロックはよく呼び出されるので(単純には n.times{ block } で n 回)、高速化はするべき箇所になります。逆に言えば伸びしろがある部分です。

良かった点:命令列を取り扱えるようにしたこと

他の貢献として、VM の命令列をプログラムから取り扱えるようにしたことです。例えば、bootsnap によるコンパイルの省略などは、事前に Ruby コードを命令列まで変換した結果をファイルに保存しておくことで、次の起動時にはそれをそのまま利用する、ということで実現します。

また、それと同じような仕組みを用いて、Ruby インタプリタの組み込みメソッドの定義を Ruby で書けるようにした、というのがあります(prelude.rb)(Ruby から C のコード片を簡単に呼び出せる仕組みを作りこんでいるので、普通の Ruby では書けないような処理が結構気楽に書けます)。例外処理などを C で書くのは凄いめんどいので、私は Ruby で組み込みクラスを定義することが多いです。

イマイチな点:毎回全部ロードしないといけない

ただ、毎回メソッド定義やクラス定義を「実行」しないといけない、というのはイマイチです(Ruby では定義は実行文なので、例えば if などでスキップできます)。つまり、起動時に定義の数 n に比例した時間がかかります。先に(実行前に)クラスやメソッドの定義が何が来るかわかっているので、それらの定義は定数時間で終わるのが理想ですね(多くのコンパイル型言語では実現している機能です)。

Lazy loading のための仕組み

また、組み込みメソッドなどはすべて利用する、というシチュエーションは稀です(例えば String#center なんて誰も使わないと思う...)。つまり、使われないメソッドにかける時間やメモリなどは省略できると良さそうです。実は、YARV には使われたときに本当にメモリからロードする Lazy loading のための仕組みだけは実装されているのですが、だれも使わんかな、と思ってオフにしています(なぜなら、利用するたびにロード済みかどうかのチェックが走るから)。この辺を積極的に利用できるようにすると、いいことあるかなぁ。

YARV のこれから

ここまで、YARV の良かった探しとイマイチ探しをしてきました。

今後の話

イマイチな点は、それはつまり伸びしろということで、例えばメソッドインライニングやブロックの呼び出し(yield)、起動処理の高速化などはまだまだやれそうな気がしています。

また、YARV の自動生成による「変更しやすい」という特性を受け継ぐような、柔軟な JIT compiler ができないか夢想しており、研究プロジェクトとして細々と進めています(例えば、暖気時間が短い、といった技術的チャレンジがあります)。そもそも 20 年くらい VM やってきたので、そろそろ別の実行系を考えてもいいんじゃないかな、とかなんとか...。

Ruby を速くするために、やれることはまだ色々ありそうです。

YARV の振り返り(個人的な話)

20年を振り返って

20年を振り返ってみると、Ruby/YARV に携わったおかげでとても面白い研究開発を続けてくることができました。また、多くのライフイベントに恵まれました。感謝しかありません。

Ruby/YARV を使っていただいてありがとうございました。今後ともよろしくお願いします。

という話をしました。

おわりに

本稿では先月に行われた EuRuKo2024 の簡単な紹介と、発表してきた内容を簡単にご紹介しました。

新型コロナウイルス感染症によって数年海外にいってなかったのですが、久しぶりに海外に行くことができ、海外での Ruby に対する熱気というようなものを感じることができて良かったです。