はじめに
Rubykaigi2023にバーチャル参加をしたので、イベントレポートを書きます。
全体的に難しくてあまり理解ができていないので、sessionのレポートというよりも参加してみて初めて知った技術や印象的だったものについて概説します。
特に印象的だったのは、以下の3つです。
- JIT
- Ractor
- RBS
JIT
JIT
とはJust In Time
の略称で、実行時コンパイラ
のことを指します。
僕はRubyKaigiに参加するまでは、インタプリタの内部実装を意識したことがなかったので、JIT
の存在すら知りませんでした。
JITコンパイラありのインタプリタがどう実行されるのかを簡単に書くと以下のようになります。
プログラム
↓ (字句解析)
トークン列
↓ (構文解析)
AST
↓
バイトコード(ネイティブの機械語ではない)
↓
JITコンパイラによって実行直前にネイティブの機械語に変換される
RubyにJIT
自体が導入されたのはそれほど最近のことではなく、Ruby2.6からでした。
今回は、Ruby3.2
からYJIT
が実用段階になったということで盛り上がりを見せていました。
インタプリタがどう動いているのかを手を動かして理解するには以下の書籍がとてもよかったです。(この書籍ではJITではなく、tree-walkingで実装されている)
Ractor
RactorはRuby3.0から導入された、並列処理のための仕組みです。
Ractorが登場するまでは、マルチスレッドによる処理は並行ではあっても並列ではありませんでした。
Ractorは並列に動作することを可能にするので、複数のスレッドが同時に別の処理を行うことができます。
0 ~ 50000
それぞれに対して$\sum_{k=0}^n k$を求める繰り返し処理を例に考えてみます。
(※ 動作環境は、MacBook Air Retina, 13-inch, 2020 1.1 GHz クアッドコアIntel Core i5 メモリ8GBです。)
- 直列処理
require 'benchmark'
def calc(i)
ans = 0
i.downto(0) do |j|
ans += j
end
end
Benchmark.bm do |x|
x.report do
(0..50000).each{|i| calc(i)}
end
end
# user system total real
# 53.037358 0.025490 53.062848 ( 53.215683)
- 並行処理
Benchmark.bm do |x|
x.report do
arr = [
Thread.new { (0..12500).each{|i| calc(i)}},
Thread.new { (12501..25000).each{|i| calc(i)}},
Thread.new { (25001..37500).each{|i| calc(i)}},
Thread.new { (37501..50000).each{|i| calc(i)}}
]
arr.each(&:value)
end
end
# user system total real
# 56.438603 0.044576 56.483179 ( 56.575608)
- Ractorを用いた並列処理
Benchmark.bm do |x|
x.report do
arr = [
Ractor.new { (0..12500).each{|i| calc(i)}},
Ractor.new { (12501..25000).each{|i| calc(i)}},
Ractor.new { (25001..37500).each{|i| calc(i)}},
Ractor.new { (37501..50000).each{|i| calc(i)}}
]
arr.each(&:take)
end
end
# user system total real
# 62.608054 0.042683 62.650737 ( 26.823120)
Ractorを使った並列処理の例がかなり早いことがわかります。
b.rb:16: warning: Ractor is experimental, and the behavior may change in
future versions of Ruby! Also there are many implementation issues.
ただ、実行時に上記のような文章が出るように、Ractorはまだまだ改善段階のようです。
RBS
RBSは、Rubyの型について記述するための言語です。
Rubyの型付けは動的であるため型注釈をすることはありませんが、このRBSを使うことによって静的に型をつけることができます。
class Person
attr_reader :first_name, :last_name, :age
def initialize(first_name:, last_name:, age:)
@first_name = first_name
@last_name = last_name
@age = age
end
def full_name
first_name + last_name
end
end
上記のようなコードの場合、以下のように型注釈をすることができます。
class Person
attr_reader first_name: String
attr_reader last_name: String
attr_reader age: Integer
def initialize: (first_name: String, last_name: String, age: Integer) -> void
def full_name: -> String
end
で、Rubyプログラムの型情報を書いただけではそんなに嬉しくないので、その型情報から型安全かをチェックします。
これはまた別のsteepというgemが必要になります。
gemをインストールし、steep init
をするとSteepfile
が生成されます。
そこに以下のように記述するとlib
ディレクトリのRubyプログラム
をsig
ディレクトリのrbsファイル
の型注釈に基づいてパースします。
target :lib do
check "lib"
signature "sig"
end
$ steep check
現状のrbsファイルは正しく記述されているため、No type error detected. 🫖
が出力されるはずです。
# ...
p = Person.new(first_name: "hello", last_name: "world", age: "1")
上記を追加し、再度steep check
をすると以下のようなエラーが出力されます。
[error] Cannot pass a value of type `::String` as an argument of type `::Integer`
│ ::String <: ::Integer
│ ::Object <: ::Integer
│ ::BasicObject <: ::Integer
│
│ Diagnostic ID: Ruby::ArgumentTypeMismatch
│
└ Person.new(first_name: "hello", last_name: "world", age: "1")
age
が型注釈のInteger
と異なっているため、弾かれていることがわかります。
終わりに
RubyKaigiのようなイベントに参加したのは初めてだったので、すごく刺激的でした。
内容はとても難しかったですが、Rubyの知見を深めるたくさんのきっかけを得ることができたと思っています。
予習をしていたらもっと楽しめたなという後悔があるので、来年は予習をして臨みたいです。
来年は沖縄で開催されるので、現地に行きたい!!