サーバーサイドエンジニアの内藤(@naitoh) です。
Rails で構築された小規模な APIサーバー(Rails の API モードで構築したもの)で、Ruby 3.2 の YJITを有効化する事で性能アップすることができましたので、喜びを分かち合いたく共有させて頂きます。
We’re able to measure real speedups ranging from 5% to 10% (depending on time of day) on our total end-to-end request completion time measurements.
YJIT を開発した Shopify では 5%から10%の処理速度の改善があったという事で、以前から弊社でも本番で運用を開始したかったのですが、比較的検証のし易い APIサーバーで本番投入の準備が整ったので導入を実施してみました。
YJIT 有効化までの道のり
1. Ruby 3.2 へのアップグレード
YJIT は Ruby 3.1 では 実験的機能の位置付けでしたが、Ruby 3.2 以降では正式にサポートされました。
- YJIT は実験段階ではなくなりました
- 1年以上にわたって本番環境でテストされ、安定して稼働する実績があります。
そのため、事前に Ruby 3.2 までアップグレードする必要があります。
2. YJIT 有効でBuildされた Ruby を用意する。
Ruby の DOCKER OFFICIAL IMAGE は YJIT 有効でBuildされているので、そちらを利用します。
$ docker pull ruby:3.2.2 $ docker run -it ruby:3.2.2 ruby --yjit -e 'p RubyVM::YJIT.enabled?' true
--yjit
オプションで YJIT有効で起動できる事が確認できました。
--yjit
オプションを付けないで起動すると YJIT 無効で起動された事がわかります。
$ docker run -it ruby:3.2.2 ruby -e 'p RubyVM::YJIT.enabled?' false
なお、YJIT無効でBuildされたRuby の場合は下記のようなエラーが出るので、この場合はYJITを利用できません。
$ ruby --yjit -e 'p RubyVM::YJIT.enabled?' ruby: warning: Ruby was built without YJIT support. You may need to install rustc to build Ruby with YJIT. -e:1:in `<main>': uninitialized constant RubyVM::YJIT (NameError) p RubyVM::YJIT.enabled? ^^^^^^
※ 補足:Ruby 3.2 の YJIT は Rust-lang で実装されているため、Build 時には Rust-lang が必要ですが、実行時は Rust-lang は不要なため、Docker IMAGE には Rust のパッケージは含まれていませんでした。
3. YJIT 有効化
環境変数 RUBY_YJIT_ENABLE=1
でも YJIT有効化が可能なので、deploy 時の環境変数に設定する事で有効化しました。
YJIT 有効化後と有効化前(1週間前の同一曜日、グラフ破線部分)との比較になります。
Rack Request : p95 Latency 比較 (低い方が性能が良い)
ave: 35.6 ms -> 34.6 ms (2.8% 短縮) とLatency が改善された事がわかります。
Shopify の実績の 5%〜10%の改善に比べると半分程度の改善になりますが、今回はAPIサーバーでフロント周りの処理が無いので妥当かもしれません。
この Rack Request の結果が、Latencyの状況全体を表しているのですが、もう少し詳細を見ていきます。
ActiveRecord Instantiation : p95 Latency 比較 (低い方が性能が良い)
Ave: 105.8 μs -> 77.9 μs (26.3% 短縮) と、こちらは改善度合いがかなり高いです。
Render Template : p95 Latency 比較 (低い方が性能が良い)
Ave: 791 μs -> 701 μs (11.3% 短縮)と、こちらも改善度合いが高いですね。
Action Controller : p95 Latency 比較 (低い方が性能が良い)
Ave: 33.1 ms -> 32.7 ms (1.2% 短縮) と、こちらは効果は低めで、オーダー的にもここが改善されると効果が顕著に出ると思われます。
Request/CPU/Memory 状況
Rack Request 状況
Max は 308 hits -> 282 hits と下がっていますが、Ave 116 hits -> 117 hits と、ほぼ同様なアクセス状況です。
CPU 使用率 (低い方が性能が良い)
Ave 0.195(各コンテナの平均) -> 0.17(各コンテナの平均) とCPU使用率が低下しています。
Memory 使用量 (低い方が性能が良い)
Ave 143(各コンテナの平均) -> 174.5(各コンテナの平均) と Memory 使用量が 22.0% 増加しています。 今回は Memory に余裕があったため大丈夫でしたが、事前にメモリの使用状況を確認してメモリに余裕がある状態でYJITを有効化した方が良さそうです。
まとめ
Memory 使用量が増える可能性はありますが、YJIT を ON にするだけで全体で 2〜3%のレイテンシ改善が見られました。 最新のRuby に追随することで Ruby 開発者の成果の恩恵を受けられるのは非常にありがたいです。
今年のRubyKaigi も直前ですが、どのような発表があるのか期待しております。
是非読者になってください
メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!
■募集ポジションはこちら
■エンジニア紹介ページはこちら