RubyKaigi2023が始まりました!!!
待ちに待ったRubyKaigiがついに始まりました。
今年はRubyKaigiにオンライン参加として、オフィスでRubyKaigiを生中継しながらワイワイする会をやりました。
Qiitaとしてはスポンサーとして協賛、ブース出展しており、現地参加メンバーもいます。
"Ractor" reconsidered
初日の今日もいくつかセッションを見ましたが、その中で理解の不十分だったこのセッションについて、過去の発表等も振り返りながら調べてみました。
Ractorについて
Ractorについては色々な記事ですでに説明されているのですが、軽くおさらいします。
RubyKaigi Takeout 2020
Ractorはマルチコアを使って並列に処理を行えるように作られました。
マルチプロセスを使う方法もありますが、データの共有に制限があったり、メモリ空間が別れていることやforkの性能の影響があったりすることで、スレッドを使いたいことがあります。
ただ、RubyのThreadはGVLにより、並列に実行することはできない制限がかかっています。またThreadプログラミングは難しい (同期等の問題やデバックの問題)という背景があり、並列実行できるようにするRactorが作られました。
Difficulties and Issues of "Ractor"
本セッションでRactorに関するDifficulties and Issuesとして挙げられていたものについて少し詳しく調べてみます。
APIについて
Ractor間で共有できるオブジェクトには制限があります。 (Shareable Objects)
- Class/module objects
- Immutable objects (frozen objects which don't refer to unshareable-objects)
- Special objects (Ractor object)
また外側のローカル変数にはアクセスできず、エラーになるという制約もあります。
そして、このオブジェクトをメッセージとして送信する方法として、deep copyして渡す方法と、move: true
を使ってshallow copyで渡す方法があります。(送信元のRactorからは参照できなくなる)
Ractor間のコミュニケーションは2種類あります。
1. 送り先のRactorを指定してpushする r.send(obj)
と Ractor.receive
r2 = Ractor.new Ractor.current do |cr|
cr.send Ractor.receive + 1
end
r1 = Ractor.new r2 do |r2|
r2.send Ractor.receive + 1
end
r1.send 1
# r1 -> r2 -> mainへ
p Ractor.receive
# => 3
2. 受信元を指定してpullする Ractor.yield(obj)
とr.take
r = Ractor.new do
val = 0
loop do
Ractor.yield val
val += 1
end
end
3.times do
puts r.take
end
# 0
# 1
# 2
それぞれ、「receiveはキューが空の時にブロックし、sendはブロックしない」「yieldはtakeされるまでブロックし、takeはyieldされるまでブロックする」という挙動をします。
これにより、以下のような挙動をします。
r = Ractor.new do
while msg = Ractor.receive
Ractor.yield msg
end
:fin
end
r.send 1
r.send 2 # rはyieldでブロックされているがキューに積む
r.send 3
r.send nil
p r.take #=> 1
p r.take #=> 2
p r.take #=> 3
p r.take #=> :fin
Poor performance
パフォーマンスに関して3点課題が挙げられていました。
- GC実行時にすべてのRactorが止まってしまう
- → Future work
- Ractorごとに1ネイティブスレッドを使うので、システムコールが増えてしまう
- → 改善予定
- Ractor.select(*rs)のオーダー
O(n)
- → 改善予定
Ractor.selectの改善
selectは複数Ractorの監視のためのメソッドです。
Ractor::RemoteError
をrescueすることで監視の役割を行うことができます。
このAPIをごそっと改善中で Ractor::Selector
というものを検討しているようです。
ネイティブスレッドの問題
MaNy ProjectとしてM:Nスレッドの説明が昨年のRubyKaigiにて発表されていました。
現状はネイティブスレッドとRubyスレッドが1:1ですが、これをM:Nにするのを目指しています。
こちらのセッションで、M:Nのメリットとして軽量になることが挙げられています。GVL(Ruby)のスケジューリングがネイティブスレッドのスケジューリングを気にせず実行できるので、オーバーヘッドが減らせるようです。
また、そもそもOSの制限により、一定以上のネイティブスレッドを作れない制限もあるようです。
M:Nスレッドを実現するために、2つのスケジューリングをRubyレイヤで行います。
- Ractor level -> M:N model
- Thread level -> 1:N model
このうち、去年は対応されていなかったRactorレベルの対応ができたことで、masterと比較してRactor作成が4.5倍、Ring exampleの処理スピードが22.6倍に向上したようです。
GCによるパフォーマンスへの影響
現時点ではまだ対応できていないようですが、Future worksとして対応が期待されます。
感想
今までうまく使われていなかったRactorですが、パフォーマンスが大幅に向上する!となり、どんどんエコシステムが発達していき、Ractorがたくさん使われるようになる未来が楽しみです。
参考