Rubiniusのしくみ
@highwide
表参道.rb #8
$whoami
@highwide (Twitter / GitHub)
(株)リブセンスでRailsを書いてます
最近は初めてのElasticsearchに四苦八苦しています
社内の「コンパイラ/インタプリタ」をテーマにしたLT大会で
Rubiniusについて調べたので、表参道.rbでも披露させてもら
おうと今日はやってきました
よろしくお願いします!
Rubiniusって何?
Rubyの処理系の1つ
CRuby: CによるRubyの実装

(MRI: Matz Ruby Implementation)
JRuby: JVMで動くRubyの実装
Rubinius:
作者: Evan Phoenixさん

(RubyKaigi3日目Keynoteスピーカー)
高速なRuby実行環境を目指している
バイトコード仮想マシンがC++で実装
LLVMによるJITコンパイルの仕組みがある
コンパイラの大半がRubyで書かれている
Ruby6割 / C++3割
コンパイラの大半が
Rubyで書かれている?
どうやって動くの?
% cat hoge.rb
10.times do |n|
puts "No. #{n}"
end
% ./rubinius/bin/rbx hoge.rb
No. 0
No. 1
No. 2
No. 3
No. 4
No. 5
No. 6
No. 7
No. 8
No. 9
例えば: この裏で行われていることは?
実行されるまでの流れ
Rubinius命令列
Rubyコード
トークン列
AST
LLVM命令列
機械語
バイトコード
コンパイル
必要に応じて
JITコンパイル
VM
実行
解釈
ちなみにMRI
YARV命令列
Rubyコード
トークン列
AST
バイトコード
コンパイル
YARV
実行
解釈
バイトコードコンパイルとは
RubyのコードをVMが理解できる命令列(バイト
コード)にコンパイルすること
ざっくり言うと
パーサがRubyのコードをバラして
AST(抽象構文木)を作って
ASTを命令列化して
命令列オブジェクトにして
プロパティを設定して
VMが読める「 .rbc」形式にします
バイトコードコンパイルの各段階を
「ステージ」と呼ぶそうです
http://rubinius.com/doc/ja/bytecode-compiler/
パーサがRubyのコードをバラして
(どう見てもMRIのパーサ)
ASTを作って
(コンパイル時に
-Aオプションで
ASTを見られる)
% ./rubinius/bin/rbx compile -A hoge.rb
@pre_exe: []
@name: :__script__
@file: "hoge.rb"
@body: 
Send
@line: 1
@name: :times
@privately: false
@vcall_style: false
@receiver: 
FixnumLiteral
@line: 1
@value: 10
@block: 
Iter
@line: 1
@locals: nil
@body: 
SendWithArguments
@line: 2
@name: :puts
@privately: true
@vcall_style: false
@block: nil
@receiver: 
Self
@line: 2
----snip----
ちょっと余談: コンパイル時に
-SオプションでS式を見られる
% ./rubinius/bin/rbx compile -S hoge.rb
[:script,
[:call,
[:lit, 10],
:times,
[:arglist,
[:iter,
[:args, :n],
[:call, nil, :puts, [:arglist, [:dstr, "No. ", [:evstr, [:lvar, :n]]]]]]]]]
% ./rubinius/bin/rbx compile -B hoge.rb
============= :__script__ ==============
Arguments: 0 required, 0 post, 0 total
Arity: 0
Locals: 0
Stack size: 2
Literals: 2: <compiled code>, :times
Lines to IP: 1: 0..9
0000: push_int 10
0002: create_block #<Rubinius::CompiledCode __block__ file=hoge.rb>
0004: send_stack_with_block :times, 0
0007: pop
0008: push_true
0009: ret
----------------------------------------
============== :__block__ ==============
Arguments: 1 required, 0 post, 1 total
Arity: 1
Locals: 1: n
Stack size: 5
Literals: 3: "No. ", :to_s, :puts
Line: 1
Lines to IP: 2: 0..14
0000: push_self
0001: push_literal "No. "
0003: push_local 0
0005: allow_private
0006: meta_to_s :to_s
0008: string_build 2
0010: allow_private
0011: send_stack :puts, 1
0014: ret
----------------------------------------
ASTを命令列化する
(コンパイル時に
-Bオプションで
バイトコードを
見られる)
% ./rubinius/bin/rbx compile -B hoge.rb
============= :__script__ ==============
Arguments: 0 required, 0 post, 0 total
Arity: 0
Locals: 0
Stack size: 2
Literals: 2: <compiled code>, :times
Lines to IP: 1: 0..9
0000: push_int 10
0002: create_block #<Rubinius::CompiledCode __block__ file=hoge.rb>
0004: send_stack_with_block :times, 0
0007: pop
0008: push_true
0009: ret
def push_int(arg1)
if arg1 > 2 and arg1 < 256
@stream << 4 << arg1
@current_block.add_stack(0, 1)
@ip += 2
@instruction = 4
else
case arg1
when -1
meta_push_neg_1
when 0
meta_push_0
when 1
meta_push_1
when 2
meta_push_2
else
push_literal arg1
----snip----
def send_stack_with_block(arg1, arg2)
@stream << 57 << arg1 << arg2
@ip += 3
@current_block.add_stack(arg2+2, 1)
@instruction = 57
end
これらのバイトコードは、Rubyの
DSLで表現されていて、仮想マシ
ン(C++)の命令と対応している
% ./rubinius/bin/rbx compile -B hoge.rb
============= :__script__ ==============
Arguments: 0 required, 0 post, 0 total
Arity: 0
Locals: 0
Stack size: 2
Literals: 2: <compiled code>, :times
Lines to IP: 1: 0..9
0000: push_int 10
0002: create_block #<Rubinius::CompiledCode __block__ file=hoge.rb>
0004: send_stack_with_block :times, 0
0007: pop
0008: push_true
0009: ret
send_stack∼といった命令列に渡されるメソッド名は
Rubiniusの中で、Rubyで定義されている
※組み込みクラスはC++で書かれているので一部C++で処理するものも。
def times
return to_enum(:times) { self } unless block_given?
i = 0
while i < self
yield i
i += 1
end
self
end rubinius/kernel/common/integer.rb
バイトコード
※Rubiniusのビルド時に
コンパイル済
言語のkernelが
Rubyで書かれている
ことがわかります
10.times do |n|
puts "No. #{n}"
end
コンパイルされた命令列を
C++で書かれた
VMが評価していきます
!RBIX
1933235666460458080
22
M
1
n
n
x
E
8
US-ASCII
10
__script__
i
10
4
10
63
0
57
1
0
21
2
17
I
2
I
0
I
0
-------snip------
hoge.rb
hoge.rbc
3行 158行
ここからが
Rubiniusの真骨頂
JITコンパイルだー!
と思うのですが...
誤解しがちなこと

(僕が誤解していたこと)
「なぜRubiniusはLLVMでコンパイルする言語なのに
CやRustほど速くないの?」
"Rubinius doesn't compile Ruby down native code, instead
it uses a JIT to compile certain blocks of code, but only
whenever possible (or deemed necessary based on the call
amounts)."
"RubiniusはRubyを機械語にコンパイルせずに、代わり
に、いくつかのブロックを可能なときだけ(あるいはそのブ
ロックの呼び出し回数に応じた必要性を判断して)、JITコンパイ
ルを行う"
誤解しがちなこと

(僕が誤解していたこと)
https://github.com/rubinius/rubinius/issues/3165
すべてのコードを
JITコンパイルで
機械語にしているわけ
じゃなかった
RubiniusのJITコンパイル
特徴: Method JIT
> メソッドが何度も呼び出されるときに行なう最適化です。この方法だと,
一度コンパイルされたメソッドは再利用できますが,コンパイルには時間が
かかり,インライン化によってメソッドのサイズが大きくなります。
http://gihyo.jp/news/report/01/rubykaigi2015/0003
命令1
命令1
命令2
命令2
命令1
命令1の

定義
命令2の

定義
インライン化
命令1の定義
命令1の定義
命令1の定義
命令2の定義
命令2の定義
LLVMが最適化しやすい
動的言語の性質を活かしたまま、静的な割当てが可能
おさらい
Rubinius命令列
Rubyコード
トークン列
AST
LLVM命令列
機械語
C
必要に応じて
JITコンパイル
VM
実行
解釈
Ruby
C++
カーネルがRubyで
書かれているということは
ActiveSupportの
メソッドの移植なんて
こともできるのでは?
Rubyで
書かれているので
さくっと
「try!」実装する
なんてことも
できました!
class Object
----snip----
def try!(*a, &b)
if a.empty? && block_given?
if b.arity.zero?
instance_eval(&b)
else
yield self
end
else
public_send(*a, &b)
end
end
end kernel/alpha.rb
class NilClass
----snip----
def try!(*args)
nil
end
end kernel/nil.rb
% cat try_length.rb

p 'hoge'.try!(:length)
p nil.try!(:length)
% ./bin/rbx try_length.rb

4
nil
「kernel/」にRubyのメソッドの定義
が書かれているので、Objectクラスと
nilクラスにtry!を定義してコンパイル
本当はRubiniusに
ぼっちオペレーター
実装しようとしたのですが
parse.yが書けずに断念しました
&.
Rubinius
思った以上に
おもしろかったです!
"Rubinius はただの Ruby 処理系では
ない。Rubinius は Ruby コ ミュニティ
にとって価値ある学習リソースだ "
「Rubyのしくみ」p330
ありがとう
ございました
「お詫び」または

「Thank you for your マサカリ」
Rubiniusのコードを完全に追えたわけではなく、書
籍やドキュメント等の情報を元にしながらポイント
ごとに実装を見て、まとめました...。
おかしいなと思ったら是非ご指摘くださいm(_ _)m
謝辞
社内でLTをやったあと、「Rubyのしくみ」
の原文や、Rubiniusのissueから、「すべて
のコードを機械語にJITコンパイルするわけ
ではない」ということを突き止めて説明して
くれた、同僚@na_o_ys氏にとっても感謝し
ています!!
Web
https://github.com/rubinius/
http://rubinius.com/doc/ja/what-is-rubinius/

- Rubinius.com 「Rubiniusとは」
https://blog.engineyard.com/2010/making-ruby-fast-the-rubinius-jit

- ENGINE YARD BLOG 「Making Ruby Fast: The Rubinius JIT」
http://gihyo.jp/news/report/01/rubykaigi2015/0003

- gihyo.jp 「RubyKaigi 2015レポート Evan Phoenixさん「Rubyを速く
するのは今がその時」 ∼RubyKaigi 2015 基調講演 3日目」
図書
Rubyのしくみ -Ruby Under a Microscope-

- Pat Shaughnessy (著), 島田 浩二 (翻訳), 角谷 信太郎 (翻訳)
参考

Rubinius Under a Microscope