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