【Ruby1.8以前から】Rubyの処理系とJIT【Ruby3.2のYJITまで】

# はじめに

Ruby3.2 で YJIT がついに実用段階になりました！

https://www.ruby-lang.org/ja/news/2022/12/25/ruby-3-2-0-released/

なので YJIT について理解するために、JIT 導入前の Ruby、MJIT、そして YJIT についてそれぞれどんな特徴があるのか簡単に調べてみました。

# Rubyの処理系

最初に Ruby の処理系についてです。**処理系**、特に **言語処理系** と呼ばれるものは、何らかのプログラミング言語を処理して実行可能にするためのソフトウェアです。Rubyの代表的な処理系は **MRI** (Matz's Ruby Interpreter) です。C言語で書かれた Ruby なので **CRuby** とも呼ばれます。

MRI 以外にも Ruby 処理系はたくさんあります。Ruby Association の以下のページに様々な処理系が紹介されています。

https://www.ruby.or.jp/ja/tech/install/ruby/implementations.html

本記事では MRI (CRuby) の簡単なしくみから YJIT までを簡単に紹介します。

# JIT が導入される前の Ruby

JIT について説明するまえに、JIT を使わない通常の Ruby がどんなものか調べてみました。
書籍「Rubyのしくみ」によると、Ruby 1.8 以前とRuby 1.9 以降で流れが少し異なります。

## Ruby 1.8 以前

Ruby 1.8 以前では、以下のような流れで Ruby のコードが実行されます。

1. **Rubyのコード** を字句解析と構文解析により **構文木(AST)** に変換
1. C言語で書かれたプログラムが **構文木** を解釈して実行

```mermaid
flowchart LR
    A[Rubyのコード] --> B[構文木]
    subgraph a[MRI _]
        B --> C[実行]
        subgraph b[Cプログラム]
        C
        end
    end
```

## Ruby 1.9 以降

Ruby 1.9 以降では、以下のような流れで Ruby のコードが実行されます。

1. **Rubyのコード** を字句解析と構文解析により **構文木(AST)** に変換
1. 構文木を **YARV命令列(バイトコード)** にコンパイル
1. YARV 命令をYARVが実行

```mermaid
flowchart LR
    A[Rubyのコード] --> B[構文木]
    subgraph a[MRI]
        B --> Z[YARV命令]
            Z --> C[実行]
        subgraph b[YARV]
        C
        end
    end
```

Ruby 1.9で **YARV** (Yet Another Ruby VM) というものが出てきました。
YARV はバイトコードを実行する仮想マシンです。YARV 命令列に変換してから YARV で実行することで、高速に実行されるようになりました。

# Ruby 2.6で導入された MJIT

Ruby 2.6で MJIT が導入されました。


JIT (Just-In-Timeコンパイラ) は実行時コンパイラとも呼ばれる、実行時にコンパイルするコンパイラです。逆に、C言語やC++など事前にコンパイルするコンパイラはAOT(Ahead-Of-Timeコンパイラ)と呼ぶそうです。

Ruby の JIT である MJIT はYARV命令列に変換する点は Ruby 1.9 以降と同じですが、YARV 命令の一部をC言語に変換、Cコンパイラに渡してコンパイル & 実行する、という特徴があります。

```mermaid
flowchart LR
    ruby[Rubyのコード] --> tree[構文木]
    subgraph a[MRI]
        tree --> yarv[YARV命令]
        yarv --> run[実行]
        subgraph b[YARV]
            run
        end
        subgraph mjit[MJIT]
            direction TB
            yarv
        end
        yarv --> clang[Cコード]
        clang --> so[.soファイル]
        so --> run
    end
```

# YJIT

そして、最後に YJIT (Yet Another Ruby JIT) です。
MJIT は YARV 命令を C言語に変換しているのに対し、 YJIT は **中間表現** (IR)を経由して機械語にまで変換を行います。
Ruby 3.1では直接機械語に変換していたため一部の環境でしか実行できませんでしたが、その中間表現を経由することで様々な環境で動作するようになり、Ruby 3.2 で実用段階になりました。

```mermaid
flowchart LR
    ruby[Rubyのコード] --> tree[構文木]
    subgraph a[MRI]
        tree --> yarv[YARV命令]
        yarv --> run[実行]
        subgraph b[YARV]
            run
        end
        subgraph yjit[YJIT]
            direction TB
            yarv
        end
        yarv --> clang[中間表現]
        clang --> so[機械語]
        so --> run
    end
```

また、ShopifyがRailsアプリケーションを高速化するために開発した JIT であり、速いことも特徴です。
Lazy Basic Block Versioning という手法が採用されており、それが高速化に関連しているようです。

https://www.estie.jp/blog/entry/2022/08/15/153357

[Ruby 3.2.0 リリースページ](https://www.ruby-lang.org/ja/news/2022/12/25/ruby-3-2-0-released/) に掲載されている以下の画像のように、ActiveRecord が 1.6倍速くなるらしいです。

![X9ulfac.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325392/3c8bf3c6-348b-2cb0-e547-62f3fd21af58.png)

# おわりに

まだまだそれぞれの詳細については調べられていないので、また時間のある時に調べてみようと思います！

余談ですが、Qiita株式会社では Ruby3 新機能の社内勉強会を開催しており、この記事もその一環で記載しました！
Devトークも募集しておりますので、社内勉強会についてや Ruby3 新機能についてお話ししましょう！

https://jobs.qiita.com/employers/qiita-inc/dev_talks/529

# 参考文献

- [Ruby 2.6にJITコンパイラをマージしました - k0kubun's blog](https://k0kubun.hatenablog.com/entry/ruby26-jit)
- [MJIT - Ruby - Ruby Issue Tracking System](https://bugs.ruby-lang.org/projects/ruby/wiki/MJIT#MJIT-organization)
- [Ruby 3.1でJITを有効化したときにRailsアプリケーションは速くなるのか - Qiita](https://qiita.com/kyntk/items/beeb8f5376dd236e54b6#yjit%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)
- https://github.com/ruby/ruby/blob/master/doc/yjit/yjit.md
- [RubyKaigi 2022 参加記 #2 - YJIT 開発チームからの発表 (Day1) _ Wantedly Engineer Blog](https://www.wantedly.com/companies/wantedly/post_articles/430807)
- Pat Shaughnessy 著、島田 浩二 訳、角谷 信太郎 訳、Rubyのしくみ　Ruby Under a Microscope, オーム社