5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ruby 4 時代の Ractor 入門:本気で並列処理するときが来た?

5
Posted at

はじめに

Ruby 4.0 が最近リリースされ、パフォーマンスと並列化に関する多くの改善がもたらされました。その中でも Ractor は CRuby における「真の並列処理」の中心的なテーマとして位置づけられています。Ruby 3 の頃は Ractor がまだ実験的機能として扱われ、多くの開発者が「試しに使ってみる」程度に留まっていましたが、Ruby 4 では Ractor の API が刷新され、より安定し、IPC(プロセス間通信)の仕組みに似た設計になりました。このポストでは、Ractor とは何か、Ruby 4 での新機能、シンプルなコード例、そして実際のシステムでいつ Ractor を使うべきか(あるいは使うべきでないか)について紹介します。

Ractor とは何か(Thread との違い)?

従来の Ruby の Thread は Global VM Lock(GVL)によって制限されており、特に CPU バウンドなタスクでは「並列」は幻想に過ぎません。一方、Ractor は独立したアクターとして設計されており、各 Ractor は独自のヒープを持ち、他の Ractor のミュータブルなオブジェクトに自由にアクセスできません。代わりに、開発者はメッセージパッシングを通じて通信を行う必要があります。このアプローチにより、Ruby は複数のコア上で真の並列実行を実現できる一方、スレッド安全性を維持しますが、データ共有に関する多くの制約が生じます。

要点:

  • Ractor はアクターモデルに基づいた抽象化で、各 Ractor は独自の「世界」を持ち、他の Ractor とミュータブルなオブジェクトを共有しません。
  • 目的:Thread + GVL で発生するデータレースを心配することなく、複数のコアで Ruby を真に並列実行させることができます。
  • Ractor 間の通信はメッセージパッシング(共有可能なオブジェクトの送受信またはコピー/移動)を通じて行われ、ミューテックスや共有状態ではありません。

Ruby 4 での Ractor の変更点

Ruby 3 → Ruby 4 で注目すべき点:

パフォーマンスの向上

  • シンボルテーブルとフローズン文字列が lock-free ハッシュセットを使用し、ロック競合を減らします。
  • メソッドキャッシュの検索、インスタンス変数(クラス/gen ivar)へのアクセスのロック削減、キャッシュ競合を回避するための Ractor ごとのカウンターを使用した割り当て。

API の再設計

  • Ractor::Port がメインの通信チャネルとして登場します。
  • Ruby 4 では旧来の Ractor.yieldRactor#take が削除され、新しい API は IPC セマンティクスに似た設計になっています。

実験的ステータスの進展

  • コアチームは、Ractor が実験的ラベルの削除に近づいていることを明確に述べており、残りのバグは主に GC に関連しています(Ruby 4.0 には Ractor ローカルGC がまだありません)。

Ruby 4 での最も目立つ変更は、Ractor::Port を中心とした新しい API です。これにより、Ractor のメッセージパッシングモデルが OS の IPC プリミティブに似た見た目になりました。同時に、シンボルテーブルの lock-free ハッシュ、メソッドキャッシュのロック削減、オブジェクト割り当て時の CPU キャッシュ競合の回避などのエンジン層での最適化により、Ractor は複数のコアでより良くスケールします。

デモコード − シングルスレッドから Ractor へ

とてもシンプルな例を構築できます。CPU バウンドな計算(例えば Fibonacci の計算)を比較します。

シンプルな CPU バウンドな関数

def fib(n)
  return n if n < 2
  fib(n - 1) + fib(n - 2)
end

順序実行

require_relative "./fib"
require "benchmark"

N = 35
COUNT = 4

puts Benchmark.measure {
  COUNT.times { fib(N) }
}

このコードはベースラインを示しており、すべて 1 つのスレッドで順序実行されます。

Thread(GVL の制限を思い出すために)

require_relative "./fib"
require "benchmark"

N = 35
COUNT = 4

puts Benchmark.measure {
  threads = COUNT.times.map do
    Thread.new { fib(N) }
  end
  threads.each(&:join)
}

多くの環境では、GVL がまだ「喉を握っている」ため、実行時間がコア数に比例して短縮されないことに気づくでしょう。

Ruby 4 の Ractor(Ractor::Port を使用)

require_relative "./fib"
require "benchmark"

N = 35
COUNT = 4

puts Benchmark.measure {
  port = Ractor::Port.new

  workers = COUNT.times.map do
    Ractor.new(port) do |port|
      while (n = port.receive)
        break if n == :stop
        fib(n)
      end
    end
  end

  COUNT.times { port.send(N) }
  workers.size.times { port.send(:stop) }

  workers.each(&:join)
}

説明:

  • Ruby 3 のように Ractor.send/receive/yield/take を直接呼び出すのではなく、Ruby 4 は Port を通信チャネルとして使用することを開発者に促しています。
  • 各ワーカーは独立した Ractor で、ポートからジョブ(数値 N)を受け取り、処理し、別のポート経由で結果を返すか yield できます。
  • ベンチマークは、CPU バウンドなワークロードがマシンと GC に応じてより良くスケールすることを示しています。

Ractor を使用するときの重要なルール

これは非常に重要なセクションであり、ドキュメントだけではなく「実戦経験」を反映した記事にするのに役立ちます:

共有可能なオブジェクトのみ自由に送信可能

  • フローズン文字列、シンボル、数字、モジュール/クラスなどのイミュータブルなものは通常共有可能です。
  • Ruby 4 は Ractor.shareable_proc を追加して、Proc の共有を容易にします。
  • ミュータブルなオブジェクトを送信する場合、Ruby はコピーまたは移動を実行し、スレッド内の共有オブジェクトとは異なるオーバーヘッドと動作につながります。

Gem と標準ライブラリの互換性

  • 多くの Gem と標準ライブラリの一部は、Ractor との完全な互換性がまだありません。
  • 実際のレポートでは、IO、Proc、ミュータブルなstructなど、多くの制限があるため、Ractor で concurrent-ruby のバックエンドを置き換えるのは困難です。
  • 多くの分析では、メイン Web プロセスに即座に導入するのではなく、バックグラウンドジョブ、バッチ処理、または内部マイクロサービス用に Ractor を使用することを推奨しています。

GC とパフォーマンス

  • コアチームは、Ractor ローカルGC が Ruby 4 では準備完了していないことを認めており、パフォーマンスの一部はグローバル GC によって制限されています。
  • 本番環境に適用する前に、十分にベンチマークを取ってください。

Rails/実際のアプリケーションに対する推奨ユースケース

  • CPU バウンドなバッチ処理:イメージ変換、ビデオエンコーディング、軽量な AI 推論を仕事キューで処理し、メインプロセスから完全に分離します。
  • 大規模データセットの分散処理:ETL およびレポートを複数のチャンクに分割し、複数の Ractor に投げ、結果を集約します。
  • アクターベースのシステムのプロトタイピング:Elixir/Go に移動せずに Ruby から出ずに Ractor の強力なコアを活用したい場合。

重要な注意事項:

  • システム全体を Ractor に書き換えるべきではなく、分離されたモジュールを選択し、本番環境で「experiments in production」として Ractor を使用してください。
  • Ractor ベースのアーキテクチャにコミットする前に、チームの実際のワークロードでベンチマークを取ってください。

結論

Ruby 4 は Ractor にとって大きな進歩を示しており、実験的なツールから Ruby での並列処理の実用的な選択肢へと変わりました。Ractor ローカルGC や Gem 互換性に関する制限は依然として存在しますが、パフォーマンスは大幅に向上し、新しい API(Port)はより理解しやすくなっています。Ruby で複数のコアを活用したいが、言語を離れたくない場合、Ractor は検討する価値があります。ただし、シンプルなユースケース、バッチ処理、またはジョブキューから始めて、本番環境に導入する前に常に十分にベンチマークを取ってください。

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?