Ruby 3.3.0 リリース
- Ruby 3.3.0 がリリースされた。新しいパーサーである Prism の導入、パーサージェネレーターとしての Lrama の採用、純粋な Ruby で書かれた JIT コンパイラー RJIT の追加、特に YJIT の性能改善が行われた。
Prism パーサー
- Prism は Ruby 言語向けの移植性が高く、エラーに強く、保守しやすい再帰下降パーサーで、デフォルト gem として提供される。
- Prism は本番環境に適しており、活発に保守されていて、Ripper の代替として利用できる。
- Prism の使い方に関する詳細なドキュメントが提供されている。
- Prism は CRuby 内部で使われる C ライブラリであると同時に、Ruby コードをパースする必要があるあらゆるツールで利用できる Ruby gem でもある。
- Prism API の主なメソッドには
Prism.parse(source), Prism.parse_comments(source), Prism.parse_success?(source) などがある。
- Prism リポジトリに直接 pull request や issue を送って貢献できる。
- Prism コンパイラーを実験的に利用するには
ruby --parser=prism または RUBYOPT="--parser=prism" を使えるが、デバッグ目的にのみ使うべきである。
Lrama パーサージェネレーター
- Bison が Lrama LALR パーサージェネレーターに置き換えられた。
- Ruby のパーサーに関する将来ビジョンを参照すると、関心のある人は確認できる。
- 保守性向上のため、内部 Lrama パーサーが Racc によって生成された LR パーサーに置き換えられた。
- パラメータ化規則 (?, *, +) をサポートし、Ruby の
parse.y で使われる予定である。
YJIT
- Ruby 3.2 と比べて大きな性能改善がある。
- splat および rest 引数のサポートが改善された。
- 仮想マシンのスタック操作に対してレジスターが割り当てられる。
- オプション引数を持つより多くの呼び出しがコンパイルされ、例外ハンドラーもコンパイルされる。
- サポートされていない呼び出しタイプやメガモーフィックなコールサイトは、もはやインタープリターへフォールバックしない。
- Rails の
#blank? や特殊な #present? のような基本メソッドがインライン化される。
Integer#*, Integer#!=, String#!=, String#getbyte, Kernel#block_given?, Kernel#is_a?, Kernel#instance_of?, Module#=== などが特別に最適化された。
- コンパイル速度は Ruby 3.2 よりわずかに高速になった。
- Optcarrot ではインタープリターより 3 倍以上高速である。
- Ruby 3.2 と比べてメモリ使用量が大きく改善された。
- コンパイル済みコードのメタデータははるかに少ないメモリで済む。
--yjit-call-threshold は 30 から、40,000 個以上の ISEQ を持つアプリケーションでは自動的に 120 に引き上げられる。
--yjit-cold-threshold が追加され、コールドな ISEQ のコンパイルをスキップする。
- Arm64 ではよりコンパクトなコードが生成される。
- コード GC はデフォルトで無効化された。
--yjit-exec-mem-size は、新しいコードのコンパイルが停止されるハードリミットとして扱われる。
- コード GC による性能低下がなく、Pitchfork を使ってサーバーが再フォークするときに、より良い copy-on-write の挙動を示す。
- 必要であれば
--yjit-code-gc でコード GC を有効化できる。
RubyVM::YJIT.enable が追加され、実行時に YJIT を有効化できる。
- Rails 7.2 はこの方法を使ってデフォルトで YJIT を有効化する予定である。
- アプリケーションのブート完了後にのみ YJIT を有効化したい場合にも、この方法を利用できる。
- 起動時に YJIT を無効化しつつ他の YJIT オプションを使うには
--yjit-disable を利用できる。
- デフォルトでさらに多くの YJIT 統計が提供される。
yjit_alloc_size および複数のメタデータ関連統計がデフォルトで提供される。
--yjit-stats によって生成される ratio_in_yjit 統計がリリースビルドで利用可能になった。もはや特別な統計ビルドや開発ビルドは不要である。
- さらに多くのプロファイリング機能が追加された。
--yjit-perf が追加され、Linux perf と組み合わせたプロファイリングが容易になった。
--yjit-trace-exits は --yjit-trace-exits-sample-rate=N を使ったサンプリングをサポートする。
- より徹底したテストと多数のバグ修正が行われた。
RJIT
- 純粋な Ruby で書かれた JIT コンパイラー RJIT が導入され、MJIT を置き換える。
- RJIT は Unix プラットフォームの x86-64 アーキテクチャでのみサポートされる。
- MJIT とは異なり、実行時に C コンパイラーを必要としない。
- RJIT は実験目的でのみ存在する。
- 本番環境では引き続き YJIT を使うべきである。
- Ruby JIT 開発に関心がある場合は、RubyKaigi の 3 日目にある k0kubun の発表を確認することが推奨される。
M:N スレッドスケジューラー
- M:N スレッドスケジューラーが導入された。
- M 個の Ruby スレッドが N 個のネイティブスレッド(OS スレッド)によって管理されるため、スレッド生成と管理のコストが減少する。
- M:N スレッドスケジューラーは C 拡張との互換性を壊す可能性があるため、デフォルトではメイン Ractor で無効化されている。
RUBY_MN_THREADS=1 環境変数を使うことで、メイン Ractor で M:N スレッドを有効化できる。
- 非メイン Ractor では常に M:N スレッドが有効である。
RUBY_MAX_CPU=n 環境変数は N の最大数(ネイティブスレッドの最大数)を設定する。デフォルト値は 8 である。
- Ractor ごとに 1 つの Ruby スレッドしか実行できないため、単一 Ractor アプリケーション(ほとんどのアプリケーション)は 1 個のネイティブスレッドしか使わない。
- ブロッキング処理をサポートするため、N より多くのネイティブスレッドが使われることがある。
性能改善
defined?(@ivar) が Object Shapes を使って最適化された。
Socket.getaddrinfo のような名前解決が、利用可能な環境で pthreads がある場合に中断可能になった。
- ガベージコレクターに関して複数の性能改善がある。
- 若いオブジェクトが古いオブジェクトから参照されても、直ちに old 世代へ昇格しなくなり、major GC の発生頻度が大幅に減少した。
- major GC を引き起こす保護されていないオブジェクト数を制御する新しい
REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO 調整変数が導入された。デフォルト値は 0.01(1%)で、major GC の頻度を大きく減らす。
- Write Barrier が欠けていた多くのコア型に対して実装が追加され、minor GC の時間と major GC の頻度が大幅に減少した。
- ほとんどのコアクラスが Variable Width Allocation を使うようになり、これらのクラスの割り当てと解放が速くなり、メモリ使用量が減り、ヒープ断片化が軽減された。
- ガベージコレクターに弱参照のサポートが追加された。
その他の注目すべき変更点
- IRB は、高度な irb:rdbg 統合、ls・show_source・show_cmds コマンドでのページャー対応、ls および show_source コマンドが提供する情報の正確性と有用性の向上、型解析を使った実験的な自動補完など、複数の改善を受けた。
- IRB は今後の改善を容易にするための大規模なリファクタリングも行われ、数十件のバグ修正も取り込まれた。
互換性の問題
- 引数なしでブロック内から
it を呼び出す方法は、今後は非推奨となり、Ruby 3.4 では最初のブロックパラメーターを参照するようになる。
- 使われていない環境変数が削除された。
標準ライブラリー更新
- RubyGems と Bundler は、ユーザーが Gemfile や gemspec に追加していない次の gem を require した場合に警告を表示する。これは、それらの gem が将来の Ruby バージョンで bundled gem になる予定だからである。
- 次の default gem が追加または更新された: prism 0.19.0, RubyGems 3.5.3, abbrev 0.1.2 など多数。
- 次の bundled gem が default gem から昇格または更新された: racc 1.7.3, minitest 5.20.0 など多数。
GN⁺ の意見
- Prism パーサー導入: Ruby 3.3.0 の最も重要な特徴の 1 つは、新しい Prism パーサーの導入だ。これにより Ruby コードをより効率的にパースでき、エラーに強く保守しやすいパーサーが提供されることで、Ruby 開発者にとって大きな助けになるだろう。
- YJIT の性能改善: YJIT の大幅な性能改善は Ruby アプリケーションの実行速度を大きく向上させ、とくにメモリ使用量の削減と GC 最適化は、大規模な Ruby アプリケーションの性能と安定性に好影響を与えるだろう。
- M:N スレッドスケジューラー: M:N スレッドスケジューラーの導入は、マルチスレッド Ruby アプリケーションの性能改善につながる可能性がある。スレッド管理コストを減らし、より効率的な並列処理を可能にするだろう。
1件のコメント
Hacker Newsの意見
Ruby 3.3の登場により、開発者の幸福を重視する言語であるRubyが、以前の遅いイメージを脱して高速さを誇るようになった。
Ruby 3.3は過去10年間で最も重要かつ機能豊富なリリースであり、Pythonより先にJITをリリースしたことに驚きを示している。
HerokuでRuby 3.3が利用可能であることを伝えている。
Ruby言語は毎年クリスマスに新しいリリースを公開している。
PythonとNodeJSをすでに知っている場合、Rubyを学ぶ価値があるのかという質問をしている。Rubyを魅力的だが難しく感じている。
Socket.getaddrinfoのような名前解決は中断される可能性がある。名前解決が必要になるたびにワーカーpthreadを作成し、getaddrinfo(3)を実行する。Prismが興味深い。Rubyコード解析ツールとしてPrismを使った例があるか質問している。
RUBY_MAX_CPU=n環境変数はネイティブスレッドの最大数を設定する。デフォルト値は8である。Prismを使った良いサンプルへのリンクを探している。リリースページでは「注目すべきAPI」以外にあまり見当たらず、失望を示している。
完璧なクリスマスプレゼントだと言及している.