RubyKaigi2023 "Ractor" reconsideredで分からなかった部分などを調べてみた

## RubyKaigi2023が始まりました！！！

待ちに待ったRubyKaigiがついに始まりました。
今年はRubyKaigiにオンライン参加として、オフィスでRubyKaigiを生中継しながらワイワイする会をやりました。
Qiitaとしてはスポンサーとして協賛、ブース出展しており、現地参加メンバーもいます。

https://zine.qiita.com/event/qiita-rubykaigi-2023-introduction/

## "Ractor" reconsidered

初日の今日もいくつかセッションを見ましたが、その中で理解の不十分だったこのセッションについて、過去の発表等も振り返りながら調べてみました。

https://rubykaigi.org/2023/presentations/ko1.html#day1

## Ractorについて

Ractorについては色々な記事ですでに説明されているのですが、軽くおさらいします。

https://github.com/ruby/ruby/blob/v3_2_2/doc/ractor.md

https://techlife.cookpad.com/entry/2020/11/20/110047

https://techlife.cookpad.com/entry/2020/12/26/131858

RubyKaigi Takeout 2020

<iframe width="560" height="315" src="https://www.youtube.com/embed/40t8EPpnujg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen loading="lazy"></iframe>

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`

```rb
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`

```rb
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されるまでブロックする」という挙動をします。

これにより、以下のような挙動をします。

```rb
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` というものを検討しているようです。

https://github.com/ruby/ruby/blob/v3_2_2/doc/ractor.md#wait-for-multiple-ractors-with-ractorselect

#### ネイティブスレッドの問題

MaNy ProjectとしてM:Nスレッドの説明が昨年のRubyKaigiにて発表されていました。

現状はネイティブスレッドとRubyスレッドが1:1ですが、これをM:Nにするのを目指しています。

<iframe width="560" height="315" src="https://www.youtube.com/embed/G0LX53QJdBE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen loading="lazy"></iframe>

こちらのセッションで、M:Nのメリットとして軽量になることが挙げられています。GVL(Ruby)のスケジューリングがネイティブスレッドのスケジューリングを気にせず実行できるので、オーバーヘッドが減らせるようです。

また、そもそもOSの制限により、一定以上のネイティブスレッドを作れない制限もあるようです。

M:Nスレッドを実現するために、2つのスケジューリングをRubyレイヤで行います。

- Ractor level -> M:N model
    - [Slide 13ページから引用](https://atdot.net/~ko1/activities/2022_rubykaigi2022.pdf)
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143317/89b85091-c0c5-8324-ac14-af7b5da907bd.png)
- Thread level -> 1:N model
    - [Slide 14ページから引用](https://atdot.net/~ko1/activities/2022_rubykaigi2022.pdf)
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143317/337b9bb7-7204-81a4-c76f-95a2398b0d36.png)

このうち、去年は対応されていなかったRactorレベルの対応ができたことで、masterと比較してRactor作成が4.5倍、Ring exampleの処理スピードが22.6倍に向上したようです。

#### GCによるパフォーマンスへの影響

現時点ではまだ対応できていないようですが、Future worksとして対応が期待されます。

## 感想

今までうまく使われていなかったRactorですが、パフォーマンスが大幅に向上する！となり、どんどんエコシステムが発達していき、Ractorがたくさん使われるようになる未来が楽しみです。

## 参考

https://github.com/ruby/ruby/blob/v3_2_2/doc/ractor.md

https://techlife.cookpad.com/entry/2020/11/20/110047

https://techlife.cookpad.com/entry/2020/12/26/131858

- ["Making *MaNy* threads on Ruby" Slide](https://atdot.net/~ko1/activities/2022_rubykaigi2022.pdf)