こんにちは、ソフトウェアエンジニアの千葉です。RubyKaigi の発表を聞いて心が踊ったり、明日の Splatoon 3 発売を控えて心が踊ったりしていて、心が三重とバンカラ地方を行ったり来たりしています。
今回、日本最大の Ruby に関するカンファレンスである RubyKaigi に Wantedly がスポンサードし、いくつかの講演を聴講させていただいています。(Wantedly はスポンサーブースを設けていて、エンジニアが執筆した技術書の配布等を行っていますので、もし興味があれば是非…🙏)
RubyKaigi の初日、YJIT 開発チームのメンバー2人から、JIT に関しての発表がありました。非常に面白い発表だったのでそれぞれ紹介させていただきます。
Building a Lightweight IR and Backend for YJIT
1つ目は、Maxime Chevalier-Boisvert さんからの 「Building a Lightweight IR and Backend for YJIT」です。YJIT の x86 以外への対応、とりわけ ARM への対応についてでした。
YJIT (Yet Another Ruby JIT) は Shopify が開発し、 Ruby 3.1 で Ruby に試験的機能として組み込まれた機能です。 (YJIT がどのような機能なのか、どういう設計で動いているかについては、既に様々な解説が上がっているのでそちらをどうぞ)
YJIT は性能向上をもたらした一方、課題としてあったのは x86 でのみ利用可能という点でした。
YJIT は YARV (Yet Another Ruby VM) のバイトコードを x86 機械語への直接変換を行っていたため、 x86 に利用が限られていました。ただ、Apple Silicon や AWS Graviton, Raspberry Pi などの ARM 環境や他のプラットフォームでも Ruby は使われており、それらも YJIT の恩恵を受けられるように、まずは ARM64, 将来的に RISC-V などの他のプラットフォームに対応が行えるように、実装の刷新を行ったとのことでした。
新しい実装の主な特徴は、YARV (Yet Another Ruby VM) のバイトコードから機械語に直接変換するのではなく、一旦 IR (Intermediate Representation) への変換を行うという工程が増えたことです。YARV バイトコードから IR への変換を行った後、その IR に対して各プラットフォーム向けの最適化を行った上で、最終的に各プラットフォームの機械語に変換する、という設計になりました。これにより、複数プラットフォームへの対応が行いやすくなったとのことです。
発表中でも IR の instruction set についての紹介がありました。実際に Ruby 本体のリポジトリからどういった instruction set なのか、どういった実装なのかを見ることが出来ます。
(ちなみに、Ruby 3.1 で Ruby 本体に組み込まれた当時は、YJIT は C 実装だったのですが、Rust 実装に置き換えが行われています。その背景については Shopify の以下のブログをどうぞ)
この実装の刷新による ARM 対応、 もう (バグがまだあるとはいえ) 動く状態になっていて、(今年末に行われるであろう) Ruby 3.2 でリリースされるという状態らしく、そのスピード感には驚きです…。これだけの刷新を短期間で進める YJIT 開発チームの開発力は凄まじいものがありますね…。Ruby 3.2 以降もパフォーマンスの向上のための最適化が進められるということで今後が楽しみです。
Towards Ruby 4 JIT
2つ目は k0kubun さんからの、「Towards Ruby 4 JIT」で、こちらは、 MJIT, YJIT 両方のこれまでと今後について、Ruby 4 に向けての今後の目標とそれに向けてのチャレンジングな点が、非常にスピーディーに発表されました。
(既に発表に使われたスライドが Speaker Deck にアップロードされております。)
とりわけ非常に興味深かった点が、MJIT が Ruby 3.1 までは C 実装だったのが、3.2 以降は Ruby 実装になり、 (Secret な) 標準ライブラリとして提供されるようになる点です。
Ruby 実装になるということは、つまりモンキーパッチが可能ということで、発表では、実際にモンキーパッチする例として、RubyVM::MJIT#compile
を差し替えて、fisk を利用しながら機械語を出力するように差し替えるモンキーパッチ、
class << RubyVM::MJIT
def compile(iseq)
buf = Fisk::Helpers.jitbuffer(4096)
Fisk.new.asm(buf) do
# pop stack frame
add rsi, imm32(0x40)
mov m64(rdi, 0x10), rsi
# return true
mov rax, imm64(0x14)
ret
end
puts "JIT compile: #{iseq.body.location.label}"
return buf.memory.to_i
end
end
RubyVM::MJIT.resume
(https://speakerdeck.com/k0kubun/rubykaigi-2022?slide=23 より)
RubyVM::MJIT::Compiler#compile
でコンパイラに食わせる C コードを変更するモンキーパッチ、
class << RubyVM::MJIT.const_get(:Compiler)
def compile(f, _iseq, funcname, _id)
c = RubyVM::MJIT.const_get(:C)
c.fprintf(f, "VALUE #{funcname}(rb_execution_context_t *ec, rb_control_frame_t *cfp)\n{\n")
c.fprintf(f, " ec->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);\n")
c.fprintf(f, " return Qtrue;\n")
c.fprintf(f, "}\n")
return true
end
end
RubyVM::MJIT.resume
(https://speakerdeck.com/k0kubun/rubykaigi-2022?slide=24 より)
などが紹介されていました。
これらを利用して、「ぼくのかんがえたさいきょうの JIT」を BYOJ (Bring Your Own JIT) してみたり、あえて壊して内部の仕様を理解したり、何かフックを仕込んでみたりするなど遊びがいがありそうです。非常に Ruby らしい、夢がある機能ですね………!!!
Ruby 3.1 → 3.2 で、YJIT が ARM にも対応されるようになり、MJIT は Ruby から触れるようになり、今後もさらなる最適化を行う予定など、 JIT の開発は勢いがあり、非常に今後が楽しみと感じさせる発表でした!
まだRubyKaigiは始まったばかりです。
Wantedlyから参加したエンジニアが他の記事をどんどん出していきます。そちらもぜひ御覧ください!
#3 次のブログへ👇