YARV - Atdot.net

Download Report

Transcript YARV - Atdot.net

Rubyプログラムを高速に
実行するための処理系の開発
日本Rubyの会
(東京農工大学 大学院)
ささだ こういち
2005 2/19 IPA 未踏ユース 成果報告会
1
発表概要







背景
目標
Ruby の特徴
設計と実装
最適化手法
現時点での性能評価
まとめ
2
Smithsonian National Museum of Natural
History
背景

オブジェクト指向スクリプト言語Ruby
• http://www.ruby-lang.org
• 使いやすいと評判
• 世界中で広く利用
• Ruby-talk ML では一日に百通以上
• 最近 Ruby on Rails が人気
• 日本発(主開発者:まつもとゆきひろ(Matz)氏)
• 日本発で世界に通用する数少ないソフトウェア
• コミュニティ活動も活発
• 日本Rubyの会
• Rubyist Magazine
3
背景:
Rubyist Magazine 0005 (Feb. 2005)
http://jp.rubyist.net/magazine/
4
背景: 現状の問題点

既存のRuby処理系は遅い
• たしかに遅いところも
• 誤った常識の流布
「Rubyって遅いんでしょ? 使えないよ」
• あなたが思うほど遅くありません
• 高速化の必要性

Ruby の処理系は魔法(by Matz)
• 魔法みたいな処理系のソースコード
• ソースコード整理の必要性
5
背景: 今までの試み

他の言語はどうしているの?

いくつかのRubyバイトコード処理系→不十分
• バイトコード(仮想マシン型)処理系が適当
• Lisp, Java, .NET, Smalltalk 処理系など
• まつもと氏 平成12年度未踏ソフトウェア創造事業
• 不完全(命令が不十分・コンパイラも無い)
• ByteCodeRuby (George Marrows氏)
• 現状のRuby処理系よりも遅い→ プロジェクト終了
• その他、作りかけばかり(新しいプロジェクトは増える)
6
7
提案:Rubyプログラムを
高速に実行するための処理系の開発




VM命令セットの設計
コンパイラの設計・開発
仮想機械(RubyVM)の設計・開発
JIT (Just In Time), AOT (Ahead of Time) コンパイラ
YARV: Yet Another Ruby VM
として開発中(オープンソースソフトウェア)
8
目標設定

世界一速いRuby処理系
• 基本性能で2倍、JITコンパイラでは5倍
AOTコンパイラでは10倍の性能向上を目指す


世界中の人に使ってもらえるように
• RubyVM
としての十分な機能を実装
いずれは次期Rubyの公式実装 Rite に
• Ruby2.0 処理系 Rite (現在 1.9.0 開発中)
• 本プロジェクトが成功すれば YARV を Rite として採用
9






Rubyの特徴
インタプリタ
クラスベースのオブジェクト指向
ダイナミック
• すべてがオブジェクト・実行時に再定義可能
• Evil Eval
どれだけ無駄を
• プログラミングは楽に、冗長に
省けるかが勝負
例外処理など大域ジャンプ可能
スレッドのサポート
Cによる拡張ライブラリ開発が容易
10
YARV 概要

単純なスタックマシン
拡張ライブラリとして実装
既存のRuby と連携して機能を利用

大部分,C言語で実装


• 今回新しく作り直さない
• ガーベージコレクタ
• Ruby Script パーサ
• Ruby C API が使える
11
デモ
12
YARVの全体像
Ruby Interpreter
拡張ライブラリとして利用
YARV
Ruby API を利用
VM 生成系
AOT コンパイラ
作る
コンパイラ
VM
JIT コンパイラ
13
YARVの動作の流れ
Ruby プログラム
コンパイラ
AOT コンパイラ
YARV 命令列
C プログラム
JIT コンパイラ
機械語
VM(実行)
C コンパイラ
拡張ライブラリ
14
今までなぜ遅かったのか?
Ruby プログラム
抽象構文木
a=
a=b+c
現在の Ruby は
構文木を直接実
行
メソッド
呼び出し(+)
b
c
15
YARV はなぜ速いのか?
Ruby プログラム
a=b+c
YARV 命令列
getlocal
getlocal
send
setlocal
b
c
+
a
a
b+c
b
c
c
b+c
b
YARV
スタックマシン
16
YARV命令セット


Ruby プログラムを表現できる命令セットを定義
スタック制御、フロー制御、メソッド定義、・・・
# Ruby program
print(“Hello”)
コンパイル
# YARV 命令列
putself
putstring “Hello”
send :print, 1
17
命令記述フォーマット

命令記述フォーマットを定義
• 各命令をこのフォーマットで記述
• 具体的な記述部分は C
• いくつか変数を宣言
• VM 実装が非常に楽に
• いろいろと自動生成
18
命令記述によるVM生成

命令記述から生成されるもののまとめ
命令記述
約2000 行
約60命令分
AOT コンパイラ
C プログラム
逆アセンブラ
ドキュメント
VM
(22000行・約400命令) これから実装
アセンブラ
コンパイラ
ベリファイア
(主に最適化器)
19
VM概要

5 つのレジスタ

3つのスタックフレーム
• PC: プログラムカウンタ
• SP: スタックポインタ
• LFP: ローカルフレームポインタ
• DFP: ダイナミックフレームポインタ
• CFP: コントロールフレームポインタ
• メソッド・クラス・ブロックスコープを管理
20
VM概要 – 例外処理

例外処理用テーブルを利用
従来の処理系(before)
begin
…
rescue
…
end
ここで毎回
setjmp
例外が起きたら
そこで longjmp
YARV (after)
begin
なにもしない
…
例外が起きたら
rescue
スタック巻き戻し
…
&例外テーブル
end
チェック
21
YARV での最適化(高速化) 一般論

冗長性の除去

計算強度の軽減

トレードオフ

マシン(その他)に依存の最適化
• 一度やったことは二度しない(キャッシュなど)
• もっと簡単にやろう
• 頻繁に実行するものを速く
• その代わりあんまり実行しないものを遅く
• マシンレジスタの利用,分岐予測の考慮,コンパイラ
の最適化の予測(確認)
22
YARVでの最適化

インラインキャッシュ
• 命令オペランドにデータを埋め込み,キャッシュ
• 定数アクセス
• 定数 A へのアクセスは遅い
• A::B::C は4命令必要(定数アクセスが3回も)
• 各定数アクセスも結構遅い
• メソッド検索
• 現在のRuby処理系は1つのハッシュでキャッシュ
23
定数アクセスの最適化
Ruby プログラム
A::B::C
getinlinecache
putnil
getconstant A
コンパイル
最適化 getconstant B
putnil
getconstant C
getconstant A
setinlinecache
getconstant B
2回目以降の実行をスキップ
getconstant C
24
YARVでの最適化(cont.)

スタックキャッシング
• スタックトップの2つをキャッシュ
• 5状態
• 命令数は5倍(各状態ごとに命令を用意)
• putself_xx_ax
• putself_ax_ab
• putself_bx_ba
• putself_ab_ba
• putself_ba_ab
25
スタックキャッシングはなぜ早いのか
Ruby プログラム
v1 = v2 + v3
YARV命令列(SC)
YARV 命令列
b+c
v1
Reg A
regA
b+c
v2
v2
v3
Reg B
regB
v3
getlocal_xx_ax
getlocal v2 v2
getlocal_xx_ab
getlocal v3 v3
スタック操作 YARV
send_ab_ax
send
++
無し! スタックマシン
setlocal_ax_xx
setlocal v1 v1
26
YARVでの最適化(cont.)

命令融合

オペランド融合

融合するものを指定すれば,自動的に生成

プロファイラ
• 頻出する連続した命令列を1命令に
• putobject putobject → putobject_x2
• putobject true → puttrue
• 命令記述を解析すれば可能
• コンパイラ(変換器)も自動生成
27
YARVでの最適化(cont.)

特化命令
• 単純な命令を特殊化
• a + b → opt_plus
if(a と b は整数か?)
if(整数の + メソッドは再定義されていないか?)
return a+b;
通常のメソッド呼び出し a.+(b) を実行
28
最適化を含むコンパイラの仕事まとめ
Ruby スクリプト
Ruby パーサ
構文木
YARV 命令列
基本コンパイラ
特化命令最適化
スタックキャッシュ命令
に変換
オペランド融合最適化
その他最適化
命令融合最適化
YARV コンパイラ
29
JIT コンパイラ



実行時に YARV 命令列を機械語に変換
コードを切り貼り(機械語を直に触りたくない)
Intel i386で、いくつかの命令だけ対応
VM評価関数
の実体
(機械語)
A を処理するところ
B を処理するところ
C を処理するところ
YARV 命令列
A
B
C
コピー
JIT コンパイル
機械語コード
A を処理するところ
B を処理するところ
C を処理するところ
30
JIT コンパイラ (cont.)


コピーするとき、相対ジャンプしている命令は
書き換えなければならない
VM 評価関数を同じものを2つ作り、機械語
(オペランド)レベルで違いがあれば書き換え
VM評価関数
の実体 1
A を処理するところ
比較
VM評価関数
の実体 2
A を処理するところ
31
AOT コンパイラ



Ruby プログラムを C プログラムに変換
命令記述を変換して貼りつけ(テキスト処理)
ほぼすべての命令に対応
命令記述
A を処理する記述
B を処理する記述
C を処理する記述
YARV 命令列
A
B
C
コピー
AOT コンパイル
C プログラム
A を処理する記述
B を処理する記述
C を処理する記述
32

ベンチマーク結果
評価環境
• (1) CPU: Intel Celeron 1.7GHz, Mem: 512MB
•
•
OS: Windows2000 + Cygwin 1.52
(2) CPU: AMD 64 2.4GHz, Mem: 1024MB
OS: Linux amd64 2.6.5-1.358 (Fedora Core 2)
コンパイラ: gcc (GCC) 3.3.3
• コンパイラオプション:-O2 -f-no-crossjumping
• 比較対象の ruby 1.9.0 (2005-02-17)
33
ベンチマーク結果(cont.)
プログラム
whileloop
times
const
method
poly_meth
block
Rescue
Rescue2
x86(Celeron)
x86_64(AMD64)
7.66
11.17
1.88
1.89
16.80
17.89
5.96
5.93
3.39
3.37
5.00
4.50
71.80
72.4
1.44
1.44
34
ベンチマーク結果(cont.)
プログラム
fib
tak
tarai
matrix
sieve
ackermann
count_words
pentomino
x86(Celeron) x86_64(AMD64)
10.90
7.73
4.94
2.12
4.92
11.18
1.12
2.09
7.90
5.73
3.95
2.41
4.46
13.00
1.03
2.02
35
ベンチマーク結果(cont.)
AOT コンパイラの効果
プログラム
whileloop
fib
Ruby
YARV
81.2 6.0 (x 11.8)
9.6
1.2 (x 8.0)
YARV AOTed
0.8 (x 102.0)
0.9 (x 10.8)
メソッド呼び出しが本質的に遅い
(バックトレース用情報などが必要になるため)
高速化手法を検討中
36
ベンチマーク結果(cont.) JIT コンパイラ
def m
1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1
end
jitcompile(self, :m)
i=0
while i<1000000
i+=1
m
end
normal YARV: 0.454000
0.000000
0.454000 ( 0.443000)
JITed YARV: 0.375000
0.000000
0.375000 ( 0.398000)
#=> 21% 高速化
37
成果物一覧

YARV ソースコード

機能一覧
• C ソースコード:17 ファイル 約 7500行
• Ruby ソースコード:5 ファイル 約 1200行
• ○:変数/制御構造・クラス/メソッド定義・メソッド呼び出し
• ○:多くの組み込み関数・組み込みクラス
• ○:高速化のためのさまざまな最適化
• △:yield(ブロック呼び出し/2.0仕様)/ defined?
• ×:内部構造に関わる組み込み関数(Thread や send)
38
品質管理

Ruby の各構文要素毎にユニットテストを作成

ベンチマークプログラムの動作を確認
• 10 ファイル
• テスト数:103、アサーション数:282
• ベンチマークプログラム数:53
• 速くなるのを見てニヤニヤする
39
対外発表など(YARV の宣伝)









8月 LL weekend
8月 まつもとさん訪問
10月 Ruby Conference 2004(バージニア)
10月 関西オープンソース 2004
1月 プログラミングシンポジウム
2月 RHG 読書会
これから:3月 情報処理学会 全国大会
これから:3月 オープンソースカンファレンス
これから:Rubyist Magazine vol. 6 (5月)~
40
開発スケジュール(過去)
7月 8月 9月 10月 11月 12月 1月 2月
見落とし発見
積み残し
命令設計
VM 設計
VM 設計
VM 設計
実装
実装
VM 実装
VM 基本部分最適化
AOT コンパイラ
JIT コンパイラ
本業(研究会原稿)
41
YARV 開発について(現在)

0.1.1 released
ホームページ

メーリングリスト

• http://www.atdot.net/yarv/
• インストール方法などもここに
• Yarv-dev (日本語)
• Yarv-devel (英語)
42
開発スケジュール(未来)

はやいうちに(来年度目標)

できるだけはやいうちに

夢
• 現在の処理系と YARV の統合
• きちんとした JIT・AOT コンパイラ
• Ruby 以外の言語 on YARV → YARV 可能性検証
• ほかいろいろ(アセンブラ、プリコンパイル、…)
• Ruby 2.0 (Rite) リリース
•
•
YARV OS(with OSKit)
組み込み機器向け YARV
43
現在の処理系と YARV の統合
Ruby Interpreter
評価器
Ruby API を利用
拡張ライブラリとして利用
YARV
コンパイラ
VM
JIT コンパイラ
44
現在の処理系と YARV の統合
Ruby Interpreter - Ruby 2.0 (Rite)
世代別 GC
多言語化
Selective Namespace
…
標コ
準ン
パ
ライ
イル
ブさ
ラれ
リた
YARV
コンパイラ
VM
JIT コンパイラ
45
開発成果のまとめ
各種最適化により順調に
高速化 10倍の性能も
 世界一速いRuby処理系
• 基本性能で2倍、JITコンパイラでは5倍
AOTコンパイラでは10倍の性能向上を目指す

世界中の人に使ってもらえるように
• RubyVM
としての十分な機能を実装
プリミティブはほぼ実装
大規模なプログラムはまだ
デバッグデバッ
現在はあまり速くなっ
てない&あまり対応
できてない
46
反省点

よかった点 ~ よくできました

悪かった点 ~ もうすこし頑張りましょう
• いろいろと宣伝した
• 考えていた最適化をいちおう全部試せた→知見を得た
• 予想以上に高速化ができた→予想どおりに Ruby が遅かった
• 大規模アプリケーションの目標をきちんとたてなかった
• ロードマップを明確化するべきだった
• Ruby 2.0 の仕様にしたが、それだと動かないプログラ
ムばっかり(現在の仕様に準拠すべきだった)
47
おわり / ご清聴ありがとうございました

Special Thanks
• IPA
• Yarv-dev / Yarv-devel ML 参加者の皆様
• NaCl まつもとゆきひろさん
• 筑波大 前田敦司助教授
• 産総研 首藤一幸さん
• And others
• RHG 読書会参加者の皆様
• Ruby Hackers
48
49
50
YARVでの最適化(cont.)

インラインキャッシュ(cont.)
• Global “VM state counter”
• VM state counter は再定義時インクリメント
• メソッド再定義
• 定数再定義
• インラインキャッシュ時,このカウンタ値を一緒に格納
• キャッシュ参照時,現在のカウンタ値と格納されたカウ
ンタ値を比較
• 同じならキャッシュヒット
• 違ったらキャッシュミス
51
VM概要 – Ensure


例外処理用テーブルを利用
Ensure 節部分をコピー
# sample
begin
A
ensure
B
end
Compiled: ExceptionTable:
Start_A: entry:
A
type: ensure
End_A:
range: Start_A – End_A
B
do: B
End_B:
restart point: End_B
end
restart sp: 0
52
-f-no-crossjumping

同じようなコードを共有しようとする
→ ジャンプ命令が増える
→ threaded code 効果半減
switch(cond){
case A:
P1; P2; break;
case B:
P3; P2; break;
}
switch(cond){
case A:
P1; L: P2; break;
case B:
P3; goto L; break;
}
53
Rubyの特徴(cont.)


クロージャ
ブロック付きメソッド呼び出し
• メソッド呼び出し時,容易に closure を渡すことが可能
# (1) in Ruby
[1,2,3].each{|e|
print e
}
;; (2) in scheme
(for-each (lambda (e)
(print e))
'(1 2 3))
54
ベンチマーク結果(cont.)

AOT Compiler の効果
i=0
while i<100000000 # 100M
i+=1
end
Ruby
YARV SC
81.2
6.8 (x11.9)
YARV AOT
0.7 (x116.0)
55
ベンチマーク結果(コンパイル時間)
if false
def m
a=1
b=2
c=3
end
# 以下 15万行繰り返し
end
ruby 1.546000 0.078000 1.624000 ( 1.648000)
yarv 6.718000 0.156000 6.874000 ( 6.913000)
56
ベンチマーク結果(コンパイル時間)
if false
a=1
b=2
c=3
... 繰り返し 16万行
End
ruby 1.281000 0.031000 1.312000 ( 1.318000)
yarv 96.188000 0.094000 96.282000 ( 96.896000)
57
命令記述フォーマット(cont.)
DEFINE_INSN
putobject
(VALUE obj)
()
(VALUE val)
{
val = obj;
}
#
#
#
#
命令名
オペランド
スタックから取る
スタックへ置く
/* C 言語で実装を記述 */
58
VM概要 – Stack Frames


Method Frame
• 他のVMとほぼ同様
• クラスフレームもほぼ同様
Control frame
• 各フレームが持つ
• レシーバ
• 命令列情報
• 継続情報(caller regs)
• CFP によりアクセス可
Stack
…
Args
Locals
Block
Environment
LFP, DFP
CFP
Self
Control
ISeq
SP
Cont.
59
VM概要 – Stack Frames (cont.)

Block Frame
• ‘yield’ によって作成
• LFP points to method
•
•
Stack
…
Args
LFP Locals
local environment
DFP points to current
environment
DFP[0] == PDFP point to
previous environment
Block
…
Args
Locals
PDFP
Ctrl
…
Args
Locals
PDFP
Ctrl
Ctrl
DFP
CFP
SP
…
…
60
VM概要 – Proc Object

Proc オブジェクトの生成
Stack
…
Args
Locals
• 要するにクロージャ
• 環境をヒープに保存
• LFP, DFP はヒープ上を指す
• スタック上の値は遡って張り直す
# Proc sample
def m arg; x = 0
iter{|a| i=1
iter{|b| j=2
Proc.new
}}; end
Block
CFP
Struct ProcObject: SP
VALUE self;
VALUE *lfp;
VALUE *dfp
VALUE iseqobj;
Proc
LFP
Env.
Ctrl
Env.
Env.
DFP
61