- tl;dr;
- モチベーション
- Ruby 3.2.0-devの何がすごいか
- Ruby 3.2.0-devでRust版のYJITを使う方法
- 計測対象のアプリケーション
- 使ったバージョン
- 比較
- まとめ
tl;dr;
モチベーション
ISUCONの事前講習*1で講師の人が「RubyはISUCON前になるとパフォーマンス改善系のコミットが入るので、自分たちのチーム(白金動物園)ではいつもruby-headを使っている」と言っていたのですが、Ruby 3.2.0-devだとRust実装のYJITが入っているのでISUCONで使うためにどれくらい性能改善してるか知っておきたかったのが一番大きなモチベーションです。
Ruby 3.2.0-devの何がすごいか
Ruby 3.1系のYJITはC実装でしたが、Ruby 3.2系ではRust実装に変更されています。
ただしRust実装のYJITは現時点ではpreview版などにも含まれていないため、Rust実装のYJITを利用するためにはRuby 3.2.0-dev( https://github.com/ruby/ruby のmasterブランチ)を利用する必要があります
Ruby 3.2.0-devでRust版のYJITを使う方法
- Rustの処理系をインストールして
--enable-yjit
をつけてRubyをビルドする必要がある- YJITを使わない場合にはRustのインストールは不要
- さらに実行時に
--yjit
を渡す
Ruby 3.2.0-devをYJITを有効化してビルドする方法は id:koic さんの下記エントリが詳しいです
計測対象のアプリケーション
達人が教えるWebパフォーマンスチューニング 〜ISUCONから学ぶ高速化の実践:書籍案内|技術評論社(通称ISUCON本) で紹介されてる https://github.com/catatsuy/private-isu を利用しました。
どの過去問を使うかにもよると思いますが、他にも何種類かやった感じだと3.2.0-devが3.1.2より悪くなってることはなかったです。(Ruby以外にボトルネックが残ってる状態だとメモリ以外はあまり変化はなかった)
使ったバージョン
Ruby 3.1.2(安定版の最新のRuby)とRuby 3.2.0-dev(7/2時点のmasterブランチでビルドした超最新のRuby)で比較してます。
isucon@isucon-01:~$ ~/local/ruby/versions/3.1.2/bin/ruby --version ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux] isucon@isucon-01:~$ ~/local/ruby/versions/3.2.0-dev/bin/ruby --yjit --version ruby 3.2.0dev (2022-07-02T10:41:02Z master 7b78aba53a) +YJIT [x86_64-linux] isucon@isucon-01:~$ ~/.cargo/bin/rustc --version rustc 1.62.0 (a8314ef7d 2022-06-27)
比較
- 競技用サーバーをisucon-01, isucon-02のようなホスト名で動かしてDatadogでメトリクスを収集
- 今回はpumaを動かしてるisucon-01のメトリクスのみ掲載
- 素の状態だと十分に性能が発揮されないので8時間の素振りでできるだけの改善を行った状態で、最後にRuby 3.2.0-devにしています
CPU
ホスト全体
CPU使用率はあまり変化なし。過去問の種類によっては3.2.0-devでちょっと減ってることがあった(誤差かも?)
Ruby 3.1.2 + YJIT
Ruby 3.2.0-dev + YJIT
pumaのmasterプロセスとworkerプロセスをピックアップ
Datadogのプロセスインテグレーション を使ってpumaのmasterプロセスとworkerプロセスのメトリクスだけ調べたのが下記になります。(具体的にはpsコマンドでとれるCPU使用率をvCPUの数で割った値をグラフに出している)
余談ですがpumaやMySQLやnginxなどISUCONで頻出のミドルウェアはプロセス単位でメトリクス見れるようにしとくとモニタリングが楽ですね。
Ruby 3.1.2 + YJIT
Ruby 3.2.0-dev + YJIT
こっちだとトータルで10%程度CPU使用率が下がってます。(誤差かも?)
Memory
ホスト全体
Ruby 3.2.0-dev + YJITだとホスト全体のメモリ使用量のトップラインがだいたい5%程度削減されています
Ruby 3.1.2 + YJIT
Ruby 3.2.0-dev + YJIT
pumaのmasterプロセスとworkerプロセスをピックアップ
pumaのプロセス単位で見ると劇的に減ってるのが分かると思います。
Ruby 3.1.2 + YJIT
Ruby 3.2.0-dev + YJIT
id:inductor さんが下記ブログで書いてるようにRuby 3.1のYJITだとメモリを喰う問題がありますが、Ruby 3.2.0-devで改善されているのが嬉しいところ。
Ruby 3.2.0-devをデプロイした瞬間のグラフ
Ruby 3.2.0-devをデプロイした瞬間のメモリのグラフを見るとpumaのメモリが一気に減ってるのが分かりやすいです。
1番目のグラフで一瞬サーバ全体のメモリ使用量が増えてるのは多分 bundle install
でnative extensionがビルドされていたからだと思います。(Rubyのマイナーバージョンが変わるとgemのインストール先も変わるため)
スコア
Ruby 3.2.0-devにするだけでスコアが1割弱上がりました。
Ruby 3.1.2 + YJIT
{"pass":true,"score":38720,"success":37371,"fail":0,"messages":[]}
Ruby 3.2.0-dev + YJIT
{"pass":true,"score":40574,"success":39244,"fail":0,"messages":[]}
まとめ
- ビルドにRustの処理系が必要になることを除けばRuby 3.2.0-devのYJITは最高
*1:https://isucon.net/archives/56735884.html
*2:本当はnginxとpumaを分けた上でpumaを2台にして計3台構成にしたかったが、nginxとpumaを別サーバーに分けるとbenchがうまく動かなかったのでこの構成になった
*3:https://zenn.dev/rosylilly/articles/202201-config-turn#workers