Ruby 3.2のYJITを有効化してみた

はじめに

こんにちは、NFT・メタバース事業部の牟田です。
この記事では、RubyKaigi2023でも発表(Optimizing YJIT’s Performance, from Inception to Production - RubyKaigi 2023)のあったYJITの有効化によるパフォーマンスの変化についてご紹介します。
Coincheck NFT(β版)のバックエンドではパフォーマンスにシビアな処理も多く、YJITの恩恵にあずかることを期待して試してみました。
結果として、大幅にパフォーマンスが向上したことを確認できました。

YJITとは

まず、そもそもYJITとはなんなのかを少しご説明すると
Shopifyが中心になって開発したJITコンパイラ(実行時にコンパイルするコンパイラ)です。

Ruby 3.1の実験段階ではx86-64のみサポートしていましたが、Ruby 3.2からは YJIT IR(中間表現)を作ってマシンコードを生成するようにしたことで、arm64/aarch64でも動かせるようになっています。

docs.google.com

YJITを有効化するための準備

それではさっそくYJITを使っていきたいところですが、YJITを利用するには以下の条件を満たす必要があります。

  • Rubyのバージョンが3.2.0以降であること
  • Rustが動く環境であること

※Ruby 3.1でもYJITは利用できますが3.2で大きく改善されているため3.2がおすすめです。

私の環境は

  • Docker(amazonlinux)
  • Ruby 3.0.4

で動作していたため、YJITを有効化する前に

  • Ruby 3.2.2へのバージョンアップ
  • Rustのインストール

を行いました。

YJITの有効化

さて、前段の準備が整ったら有効化するのは簡単です。
環境変数RUBY_YJIT_ENABLEを1に設定するだけでYJITの有効化は完了です。

ENV RUBY_YJIT_ENABLE=1

以下のコマンドを実行します。

pry(main)> p RubyVM::YJIT.enabled?
true

trueが返ってくることが確認できました。
これでYJITが有効化されました。

また、Docker Image上でRuby 3.2とRustの環境がある場合は、--yjitオプションをつけてrubyを起動することでも有効化できます。
例えば、Docker Hubのruby:3.2.2であれば条件を満たしています。

 $ docker run -it ruby:3.2.2 ruby --yjit -e 'p RubyVM::YJIT.enabled?'
true

※YJITを利用できない環境下では以下のようになります。

pry(main)> 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?
        ^^^^^^

パフォーマンスの確認

Coincheck NFT(β版)でNFTを一覧表示する際に利用されるAPIのパフォーマンスを確認していこうと思います。

require 'benchmark'
require 'net/http'

Benchmark.bm do |x|
  x.report "get_tokens" do
    Net::HTTP.start('localhost', 3000) do |http|
      http.get('xxx') # 複数のNFTをgetするAPI
    end
  end
end

YJITがfalseの場合

                      user           system        total             real
get_tokens  0.003551   0.003944   0.007495 (  1.127334)

YJITがtrueの場合

                      user           system        total             real
get_tokens  0.001051   0.002386   0.003437 (  1.071347)

totalを見ると、

  • YJIT無し:0.007495
  • YJIT有り:0.003437

となっており、約44%改善されています。
本番環境で計測するとまた違った結果になるかもしれませんが、パフォーマンスが改善される可能性はありそうです。

上記以外にもCoincheck NFT(β版)で頻繁に実行される処理をいくつか比較してみました。

有効前(s) 有効後(s) 改善率
一つのNFTをgetするAPI 0.000560 0.000541 約3%
NFTを入庫する処理 1.429257 1.360675 約16%
メタデータを更新する処理 0.176090 0.137043 約22%

まとめ

本記事では、YJITを有効化したことによるパフォーマンスの変化についてご紹介しました。

Rustが必要になりますが難しい設定を必要とせずパフォーマンス改善ができるのは本当にすごいと思います。
(MJIT&YJITの開発に携わっている国分さんが開発されているRJITも気になります)

今後、本番環境でもYJITを利用できるようにしていきたいです。

終わりに

コインチェックに興味がある方に向けて社内の情報をゆるく発信していくCoincheck FMも配信しているので、よろしければ聞いてみていただけると幸いです。

open.spotify.com