Ufuk's explanation of Ruby 3.2 achievements by Shopify
Shopify の Ufuk さんが、Twitter で Shopify が Ruby 3.2 に成した貢献を連投されていたので、許可をえて訳したものをまとめておきます。誤訳などあればご指摘ください。なお、訳はDeepL さんの力を借りて行いました。というかだいたいそのまんまです。
いやぁ、スゴイ成果ですね。
Ruby 3.2 のクリスマスリリースが近づいているので、とても興奮しています。実際、3.2 には非常に多くの優れた機能があり、リリース日が待ちきれませんでしたが、われわれはすでに Shopify のお店のすべてのリクエストに対してデプロイしています。
すべての詳細について詳しく説明させてください🧵
訳注:棒グラフY軸が 0.9 から始まっているのに注意。See also: グラフの描き方
まず一番楽しみにしている機能はYJITです。Ruby 3.2のYJITは、プロダクションレディと銘打っています。これは、以前のYJITリリースでの大きなメモリオーバーヘッドという制作上の最大の欠点を解決することができたからです。
現在、YJITは全体としてより少ないメモリを使用します(デフォルトの実行可能メモリサイズの設定を256MBから64MBに減らしたほどです)。また、必要なメモリを遅延して割り当てるので、たとえ小さなアプリであっても、もうパラメータを調整する必要はありません。
訳注:See also YJITで使ったRustの省メモリ化テクニック - Qiita
さらに、生成されたマシンコードのうち、有効でなくなったものや不要になったものを無効にして回収するCode GCが搭載されました。これにより、実行可能なメモリサイズをいっぱいに使っているアプリケーションでも、一部のメモリを回収し、JITの恩恵を受け続けることができるようになりました。
トレードオフが必要なのでは? いやいや、そんなことはありません。YJITは、メモリをより効率的に使用しているにもかかわらず、従来のYJITよりもさらに高速になり、railbenchではCRubyよりも最大40%も高速になりました。 詳しくは YJIT Benchmarks | Benchmarks and continuous checks for YJIT performance. をご覧ください。
さらに、JITを再構築して、マシンコードを生成するバックエンドをフロントエンドから分離し、既に存在していたx64バックエンドを補完するARMバックエンドを追加できるようにしました。これで、RPisやGravitonサーバーでYJITを実行できるようになりました。
YJITチームは、年初に野心的なロードマップを設定し、ロードマップにないRustの書き換えを決定し、チームを成長させ、それでも11月末までに全てのアイテムを納品しました。これは、以下の人々の素晴らしい努力によるものです。
@Love2Code, @alanwusx, @codefolio, @kddnewton, @k0kubun & @jimmyhmiller
これらの作業により、店舗アプリでの総ウェブリクエスト時間は平均10%高速化されました。これは、ウェブサーバーがIOでブロックされている時間、例えばDBからのデータを待っている時間なども含まれますが、YJITでは明らかにこれ以上速くすることはできません。
しかし、このリリースでエキサイティングなのはYJITだけではありません。Ruby 3.2 は、VWA (Variable Width Allocation) をデフォルトで有効化した最初のリリースでもあるのです。VWAは、Rubyの仮想マシンのメモリローカリティを改善するために2年前に開始したプロジェクトです。
Ruby 3.1 までは、Ruby VM は常に 40 バイトの固定スロットを使って Ruby オブジェクトを割り当てていました。40バイトのうちいくつかは管理用に使われ、残りの20バイトぐらいのバイトにデータを格納することができます。しかし、ほとんどのオブジェクトはそれ以上のデータを格納します。
訳注:データを格納できるのは、(64ビットCPUにおいて)24バイト(3 words)に、ものによってはもう少し、って感じです。
そのため、Rubyはそこに余分なデータを格納するために、システムから余分なメモリを確保しなければならなくなります。その余分なメモリの場所は、オブジェクトスロットの場所から遠く離れてしまうので、オブジェクトを読み込むために余分なメモリの読み込みが必要になり、効率が悪くなってしまうのです。
訳注:遠くのメモリをアクセスすると、CPUのキャッシュが効かないことが多いためです。
VWAプロジェクトでは、複数のサイズのスロットを実装し、より大きなオブジェクトがそのデータをスロットに直接格納できるようにしました。これにより、メモリの局所性が改善され、オブジェクトへのアクセスが高速化されます。VWAはRuby 3.1にも入っていましたが、コンパイルフラグで無効がデフォルトになっていました。Ruby 3.2 では、これがデフォルトで有効になりました。
その結果、どうなったって? よくぞ聞いてくださいました。VWAを使用しない場合と比較して、現実的なベンチマークでは、VWAを使用したデフォルトのRuby 3.2のパフォーマンスは、2%~10%向上しています。
訳注:棒グラフY軸が 0.90 から始まっているのに注意。See also: グラフの描き方
この成果は、@peterzhu2118, @eightbitraptor, および @tenderlove によるものです。より詳しくは、Peter and Matt による RubyKaigi 2021 の発表 を参照してください。
ここで、Rubyを高速化するための最後のピース、「Object Shapes」を紹介します。オブジェクトシェイプとは、オブジェクトのプロパティを効率的に格納するための技術です。この技術はSmalltalkまでさかのぼり、現在V8やTruffleRuby、その他のVMで使われています。
訳注:See also ss2021 - 日本ソフトウェア科学会 プログラミング論研究会 (JSSST-SIGPPL) (Special Interest Group on Programming and Programming Languages) の鵜川先生の講演資料: https://tugawa.github.io/files/jssst-ss-2021.pdf
このアイディアは、オブジェクトをプロパティの集まりとして扱い、同じプロパティを同じように定義したオブジェクトは、同じ「形」を持っていると考えるというものです。プロパティとは、インスタンス変数や frozen の状態など、オブジェクトが保持する「状態」のことです。
訳注:クラスに似てますが、インスタンス変数はプログラムの進行状況によって増えたりするので、若干違うものです。Object Shape は性能向上のために用いられます。
このような Object Shape を使ってオブジェクトを表現することで、他の方法では不可能な最適化を実現することができるのです。@ChrisGSeaton は、このアイデアを説明する素晴らしいRubyKaigi 2021の講演を行いました: The Future Shape of Ruby Objects
YJITの仕事の過程で、@Love2Code は、RubyがこのObject Shapesの技術を実装すれば、インタープリタとJITの両方でインスタンス変数へのアクセスを改善できることに気づきました。彼女はそのアイデアの一部をRubyKaigi 2021の講演で説明しました:[EN] YJIT - Building a new JIT Compiler inside CRuby / Maxime Chevalier-Boisvert @maximecb - YouTube
この方向性に突き動かされ、今年の初めには @JemmaIssroff と @tenderloveとで小さなチームを立ち上げ、オブジェクトシェイプの実装に取り組みました。その結果は、驚くべきものでした。最終的には、YJITでインスタンス変数の書き込みを6倍高速化することができました。🚀
これがどのように実装されているのか、あるいはどのようにパフォーマンス向上につながるのかを理解するためには、Euruko 2022のJemmaによる素晴らしい講演を見ることをお勧めします: Implementing Object Shapes in CRuby by Jemma Issroff - YouTube
私たちは、Ruby 3.2でオブジェクトシェイプの上に実装するアイデアをまだたくさん持っています。
ふぅ、すでに長いスレッドになってしまいましたが、今度のリリースで私がわくわくするのは、パフォーマンスの向上だけではありません。
では、それ以外に何があるのでしょうか? 安定性です。
Shopifyでは、常に新しいバージョンのRubyに移行することに意欲的でしたが、長年にわたってそれは容易ではありませんでした。
昨年、@_byroot は、Ruby 3.1 の安定性のために、私たちの大規模なプラットフォームを利用する努力をし、それが実を結びました。リリースからわずか18日後、実際には休暇から戻って3日後に、Ruby 3.1.0 を Core monolith 上で動かすことができたのです。
今年、私たちはこのアプローチにさらに力を入れました。今年の初めから、Ruby HEAD のナイトリーチェックアウトに対して Core monolith CI を走らせています。もしクラッシュがあれば、私たちはそれを調査するか、アップストリームに報告することにしています。
訳注:Ruby のバグトラッカなどにいくつか報告をいただいております。ありがたい限りです。
そこで、今年一年を通して、私たちはRubyの安定性のために投資してきました。2022年9月初旬からは、CoreモノリスCIで1時間ごとにRubyのHEADチェックアウトを実行し、さらに取り組みを強化しました。また、新しいYJITバックエンドをテストするために、アーリーアクセスのARMクラスタをセットアップしました。
この作業により、いくつかの不明瞭なバグが確認されたため、リリース前に修正し、リリース後数日で再びRuby 3.2を実行できるようにしました。しかし今回は、リリース前に 3.2 を店舗アプリにリリースすることになり、当社の業績と加盟店様のために大きく向上させることができました。
もちろん、まだ終わりではありませんし、今後のリリースも意欲的に計画しています。@kddnewton は、彼が過去4ヶ月間取り組んできた新しい Ruby パーサの初期バージョンをオープンソース化しました。
Shopify/yarp: Yet Another Ruby Parser
この新しいパーサーは完成までに時間がかかりますが、Ruby DXチームが取り組んでいるRuby LSPサーバー(Shopify/ruby-lsp: An opinionated language server for Ruby)などの開発者向けツールに採用することで、早期にその効果が得られると期待しています。
もちろん、YJIT、GC、Object Shapesにもさらなる改良が加えられる予定です。すべてのチームのロードマップが明らかになるにつれて、ShopifyのRubyインフラチームが新年に取り組むすべてのクールな事柄について、より詳細な情報をお伝えする予定です。ご期待ください。
訳注:「すでに長いスレッド」からも長くてスゴイ。いろいろすごい。来年が楽しみですね。
Discussion