Ruby プログラムを高速に 実行するための処理系の開発

61
1 Ruby プププププププププ プププププププププププププ 日日 Ruby 日日 日日日日日日 日日日 日日日 日日日日 2005 2/19 IPA 日日日日日 日日日日日

description

Ruby プログラムを高速に 実行するための処理系の開発. 日本 Ruby の会 (東京農工大学 大学院) ささだ こういち. 2005 2/19 IPA 未踏ユース 成果報告会. 発表概要. 背景 目標 Ruby の特徴 設計と実装 最適化手法 現時点での性能評価 まとめ. Smithsonian National Museum of Natural History. 背景. オブジェクト指向スクリプト言語 Ruby http://www.ruby-lang.org 使いやすいと評判 世界中で広く利用 - PowerPoint PPT Presentation

Transcript of Ruby プログラムを高速に 実行するための処理系の開発

Page 1: Ruby プログラムを高速に 実行するための処理系の開発

1

Ruby プログラムを高速に実行するための処理系の開発

日本 Ruby の会(東京農工大学 大学院)

ささだ こういち

2005 2/19 IPA 未踏ユース 成果報告会

Page 2: Ruby プログラムを高速に 実行するための処理系の開発

2

発表概要 背景 目標 Ruby の特徴 設計と実装 最適化手法 現時点での性能評価 まとめ Smithsonian National Museum of Natural History

Page 3: Ruby プログラムを高速に 実行するための処理系の開発

3

背景 オブジェクト指向スクリプト言語 Ruby

• http://www.ruby-lang.org

• 使いやすいと評判• 世界中で広く利用

• Ruby-talk ML では一日に百通以上• 最近 Ruby on Rails が人気

• 日本発(主開発者:まつもとゆきひろ( Matz )氏)• 日本発で世界に通用する数少ないソフトウェア

• コミュニティ活動も活発• 日本 Ruby の会• Rubyist Magazine

Page 4: Ruby プログラムを高速に 実行するための処理系の開発

4

背景 : Rubyist Magazine 0005 (Feb. 2005)

http://jp.rubyist.net/magazine/

Page 5: Ruby プログラムを高速に 実行するための処理系の開発

5

背景 : 現状の問題点 既存の Ruby 処理系は遅い

• たしかに遅いところも• 誤った常識の流布

「 Ruby って遅いんでしょ? 使えないよ」•あなたが思うほど遅くありません

• 高速化の必要性 Ruby の処理系は魔法( by Matz )

• 魔法みたいな処理系のソースコード• ソースコード整理の必要性

Page 6: Ruby プログラムを高速に 実行するための処理系の開発

6

背景 : 今までの試み 他の言語はどうしているの?

• バイトコード(仮想マシン型)処理系が適当• Lisp, Java, .NET, Smalltalk 処理系など

いくつかの Ruby バイトコード処理系→不十分• まつもと氏 平成 12 年度未踏ソフトウェア創造事業

• 不完全(命令が不十分・コンパイラも無い)

• ByteCodeRuby (George Marrows 氏 )

• 現状の Ruby 処理系よりも遅い→ プロジェクト終了

• その他、作りかけばかり(新しいプロジェクトは増える)

Page 7: Ruby プログラムを高速に 実行するための処理系の開発

7

Page 8: Ruby プログラムを高速に 実行するための処理系の開発

8

提案: Ruby プログラムを高速に実行するための処理系の開発

VM 命令セットの設計 コンパイラの設計・開発 仮想機械 (RubyVM) の設計・開発 JIT (Just In Time), AOT (Ahead of Time) コンパイ

YARV: Yet Another Ruby VMとして開発中(オープンソースソフトウェア)

Page 9: Ruby プログラムを高速に 実行するための処理系の開発

9

目標設定世界一速い Ruby 処理系

• 基本性能で 2 倍、 JIT コンパイラでは 5 倍AOT コンパイラでは 10 倍の性能向上を目指す

世界中の人に使ってもらえるように• RubyVM としての十分な機能を実装

いずれは次期 Ruby の公式実装 Rite に• Ruby2.0 処理系 Rite  (現在 1.9.0 開発中)• 本プロジェクトが成功すれば YARV を Rite とし

て採用

Page 10: Ruby プログラムを高速に 実行するための処理系の開発

10

Ruby の特徴 インタプリタ クラスベースのオブジェクト指向 ダイナミック

• すべてがオブジェクト・実行時に再定義可能• Evil Eval

• プログラミングは楽に、冗長に 例外処理など大域ジャンプ可能 スレッドのサポート C による拡張ライブラリ開発が容易

どれだけ無駄を省けるかが勝負

Page 11: Ruby プログラムを高速に 実行するための処理系の開発

11

YARV 概要 単純なスタックマシン 拡張ライブラリとして実装 既存の Ruby と連携して機能を利用

• 今回新しく作り直さない• ガーベージコレクタ• Ruby Script パーサ• Ruby C API が使える

大部分,C言語で実装

Page 12: Ruby プログラムを高速に 実行するための処理系の開発

12

デモ

Page 13: Ruby プログラムを高速に 実行するための処理系の開発

13

YARV の全体像

Ruby InterpreterRuby Interpreter

YARVYARV

JIT コンパイラ

コンパイラ

拡張ライブラリとして利用

Ruby API を利用

VM

AOT コンパイラVM 生成系 作る

Page 14: Ruby プログラムを高速に 実行するための処理系の開発

14

YARV の動作の流れRuby プログラム

コンパイラ

YARV 命令列

VM (実行)

JIT コンパイラ

AOT コンパイラ

C プログラム

C コンパイラ

拡張ライブラリ機械語

Page 15: Ruby プログラムを高速に 実行するための処理系の開発

15

今までなぜ遅かったのか?

a =

メソッド呼び出し(+)

cb

抽象構文木Ruby プログラム

a = b + c

a =

メソッド呼び出し(+)

cb

a =

メソッド呼び出し(+)

cb現在の Ruby は構文木を直接実

Page 16: Ruby プログラムを高速に 実行するための処理系の開発

16

YARV はなぜ速いのか?

Ruby プログラムa = b + c

getlocal bgetlocal csend +setlocal a

YARV 命令列

YARV

a

b

c b

c

b+c

b+c

スタックマシン

Page 17: Ruby プログラムを高速に 実行するための処理系の開発

17

YARV 命令セット Ruby プログラムを表現できる命令セットを

定義 スタック制御、フロー制御、メソッド定義、

・・・# Ruby programprint(“Hello”) # YARV 命令列

putselfputstring “Hello”send :print, 1コンパイル

Page 18: Ruby プログラムを高速に 実行するための処理系の開発

18

命令記述フォーマット 命令記述フォーマットを定義

• 各命令をこのフォーマットで記述• 具体的な記述部分は C

• いくつか変数を宣言• VM 実装が非常に楽に

• いろいろと自動生成

Page 19: Ruby プログラムを高速に 実行するための処理系の開発

19

命令記述による VM 生成 命令記述から生成されるもののまとめ

命令記述約 2000 行

約 60 命令分

コンパイラ(主に最適化器)

VM(22000 行・約 400 命令 )

ベリファイア

逆アセンブラ

アセンブラ

ドキュメント

C プログラムAOT コンパイラ

これから実装

Page 20: Ruby プログラムを高速に 実行するための処理系の開発

20

VM 概要 5 つのレジスタ

• PC: プログラムカウンタ• SP: スタックポインタ• LFP: ローカルフレームポインタ• DFP: ダイナミックフレームポインタ• CFP: コントロールフレームポインタ

3 つのスタックフレーム• メソッド・クラス・ブロックスコープを管理

Page 21: Ruby プログラムを高速に 実行するための処理系の開発

21

VM 概要 – 例外処理 例外処理用テーブルを利用

begin …rescue …end

ここで毎回 setjmp

例外が起きたら

そこで longjmp

begin …rescue …end

なにもしない

例外が起きたら

スタック巻き戻し

&例外テーブルチェック

従来の処理系( before)

YARV ( after )

Page 22: Ruby プログラムを高速に 実行するための処理系の開発

22

YARV での最適化(高速化) 一般論冗長性の除去

• 一度やったことは二度しない(キャッシュなど) 計算強度の軽減

• もっと簡単にやろう トレードオフ

• 頻繁に実行するものを速く• その代わりあんまり実行しないものを遅く

マシン(その他)に依存の最適化• マシンレジスタの利用,分岐予測の考慮,コンパ

イラの最適化の予測(確認)

Page 23: Ruby プログラムを高速に 実行するための処理系の開発

23

YARV での最適化 インラインキャッシュ

• 命令オペランドにデータを埋め込み,キャッシュ

• 定数アクセス• 定数 A へのアクセスは遅い• A::B::C は 4 命令必要(定数アクセスが3回も)• 各定数アクセスも結構遅い

• メソッド検索• 現在の Ruby 処理系は 1 つのハッシュでキャッシュ

Page 24: Ruby プログラムを高速に 実行するための処理系の開発

24

定数アクセスの最適化

putnilgetconstant Agetconstant Bgetconstant C

getinlinecacheputnilgetconstant Agetconstant Bgetconstant Csetinlinecache

2 回目以降の実行をスキップ

Ruby プログラムA::B::C

最適化コンパイル

Page 25: Ruby プログラムを高速に 実行するための処理系の開発

25

スタックキャッシング• スタックトップの 2 つをキャッシュ• 5 状態• 命令数は 5 倍(各状態ごとに命令を用意)

• putself_xx_ax

• putself_ax_ab

• putself_bx_ba

• putself_ab_ba

• putself_ba_ab

YARV での最適化 (cont.)

Page 26: Ruby プログラムを高速に 実行するための処理系の開発

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

Page 27: Ruby プログラムを高速に 実行するための処理系の開発

27

命令融合• 頻出する連続した命令列を 1 命令に• putobject putobject → putobject_x2

オペランド融合• putobject true → puttrue

融合するものを指定すれば,自動的に生成• 命令記述を解析すれば可能• コンパイラ(変換器)も自動生成

プロファイラ

YARV での最適化 (cont.)

Page 28: Ruby プログラムを高速に 実行するための処理系の開発

28

特化命令• 単純な命令を特殊化• a + b → opt_plus

YARV での最適化 (cont.)

if ( a と b は整数か?)

if (整数の + メソッドは再定義されていないか?)

return a+b;

通常のメソッド呼び出し a.+(b) を実行

Page 29: Ruby プログラムを高速に 実行するための処理系の開発

29

最適化を含むコンパイラの仕事まとめ

構文木

YARV コンパイラ

YARV 命令列

基本コンパイラ特化命令最適化

オペランド融合最適化

その他最適化命令融合最適化

スタックキャッシュ命令に変換

Ruby スクリプト Ruby パーサ

Page 30: Ruby プログラムを高速に 実行するための処理系の開発

30

機械語コード

JIT コンパイラ 実行時に YARV 命令列を機械語に変換 コードを切り貼り(機械語を直に触りたく

ない) Intel i386 で、いくつかの命令だけ対応VM 評価関数

の実体(機械語)

YARV 命令列ABC

B を処理するところC を処理するところ

A を処理するところB を処理するところC を処理するところ

A を処理するところコピー

JIT コンパイル

Page 31: Ruby プログラムを高速に 実行するための処理系の開発

31

JIT コンパイラ (cont.)

コピーするとき、相対ジャンプしている命令は書き換えなければならない

VM 評価関数を同じものを 2 つ作り、機械語(オペランド ) レベルで違いがあれば書き換えVM 評価関数

の実体  1

A を処理するところ

VM 評価関数の実体  2

A を処理するところ比較

Page 32: Ruby プログラムを高速に 実行するための処理系の開発

32

AOT コンパイラ Ruby プログラムを C プログラムに変換 命令記述を変換して貼りつけ(テキスト処

理) ほぼすべての命令に対応命令記述 YARV 命令

列ABC

B を処理する記述C を処理する記述

A を処理する記述B を処理する記述C を処理する記述

A を処理する記述コピー

AOT コンパイル

C プログラム

Page 33: Ruby プログラムを高速に 実行するための処理系の開発

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)

Page 34: Ruby プログラムを高速に 実行するための処理系の開発

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

Page 35: Ruby プログラムを高速に 実行するための処理系の開発

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

Page 36: Ruby プログラムを高速に 実行するための処理系の開発

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 コンパイラの効果

メソッド呼び出しが本質的に遅い(バックトレース用情報などが必要になるため)

高速化手法を検討中

Page 37: Ruby プログラムを高速に 実行するための処理系の開発

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% 高速化

Page 38: Ruby プログラムを高速に 実行するための処理系の開発

38

成果物一覧 YARV ソースコード

• C ソースコード: 17 ファイル 約 7500 行• Ruby ソースコード: 5 ファイル 約 1200 行

機能一覧• ○:変数 / 制御構造・クラス / メソッド定義・メソッド呼

び出し• ○:多くの組み込み関数・組み込みクラス• ○:高速化のためのさまざまな最適化• △: yield (ブロック呼び出し /2.0仕様) / defined?

• × :内部構造に関わる組み込み関数( Thread や send )

Page 39: Ruby プログラムを高速に 実行するための処理系の開発

39

品質管理 Ruby の各構文要素毎にユニットテストを

作成• 10 ファイル• テスト数: 103 、アサーション数: 282

ベンチマークプログラムの動作を確認• ベンチマークプログラム数: 53

• 速くなるのを見てニヤニヤする

Page 40: Ruby プログラムを高速に 実行するための処理系の開発

40

対外発表など( YARV の宣伝) 8月 LL weekend 8月 まつもとさん訪問 10月 Ruby Conference 2004 (バージニア) 10月 関西オープンソース 2004 1月 プログラミングシンポジウム 2月 RHG 読書会 これから: 3月 情報処理学会 全国大会 これから: 3月 オープンソースカンファレンス これから: Rubyist Magazine vol. 6 ( 5月)~

Page 41: Ruby プログラムを高速に 実行するための処理系の開発

41

開発スケジュール(過去)7月  8月  9月  10月  11月  12月  1月  2月

VM 設計

VM 基本部分最適化AOT コンパイラ

JIT コンパイラ

命令設計

VM 実装VM 設計

実装VM 設計

実装

見落とし発見

積み残し

本業(研究会原稿)

Page 42: Ruby プログラムを高速に 実行するための処理系の開発

42

YARV 開発について(現在) 0.1.1 released ホームページ

• http://www.atdot.net/yarv/

• インストール方法などもここに メーリングリスト

• Yarv-dev (日本語)• Yarv-devel (英語)

Page 43: Ruby プログラムを高速に 実行するための処理系の開発

43

開発スケジュール(未来) はやいうちに(来年度目標)

• 現在の処理系と YARV の統合• きちんとした JIT ・ AOT コンパイラ• Ruby 以外の言語 on YARV → YARV 可能性検証• ほかいろいろ(アセンブラ、プリコンパイル、

…) できるだけはやいうちに

•Ruby 2.0 ( Rite ) リリース 夢

• YARV OS ( with OSKit )• 組み込み機器向け YARV

Page 44: Ruby プログラムを高速に 実行するための処理系の開発

44

現在の処理系と YARV の統合

YARVYARV

JIT コンパイラ

コンパイラ

拡張ライブラリとして利用

Ruby API を利用 VM

評価器Ruby InterpreterRuby Interpreter

Page 45: Ruby プログラムを高速に 実行するための処理系の開発

45

現在の処理系と YARV の統合

YARVYARV

JIT コンパイラ

コンパイラVM

Ruby Interpreter - Ruby 2.0 (Rite)Ruby Interpreter - Ruby 2.0 (Rite)

世代別 GC

多言語化Selective Namespace

コンパイルされ

た標準ライブラリ

Page 46: Ruby プログラムを高速に 実行するための処理系の開発

46

開発成果のまとめ世界一速い Ruby 処理系

• 基本性能で 2 倍、 JIT コンパイラでは 5 倍AOT コンパイラでは 10 倍の性能向上を目指す

世界中の人に使ってもらえるように• RubyVM としての十分な機能を実装

各種最適化により順調に高速化  10 倍の性

能も

現在はあまり速くなってない&あまり対応できてない

プリミティブはほぼ実装大規模なプログラムはま

だデバッグデバッ

Page 47: Ruby プログラムを高速に 実行するための処理系の開発

47

反省点 よかった点 ~ よくできました

• いろいろと宣伝した• 考えていた最適化をいちおう全部試せた→知見を得た• 予想以上に高速化ができた→予想どおりに Ruby が遅かった

悪かった点 ~ もうすこし頑張りましょう• 大規模アプリケーションの目標をきちんとたてなかっ

た• ロードマップを明確化するべきだった• Ruby 2.0 の仕様にしたが、それだと動かないプログ

ラムばっかり(現在の仕様に準拠 すべきだった)

Page 48: Ruby プログラムを高速に 実行するための処理系の開発

48

おわり / ご清聴ありがとうございました

Special Thanks• IPA

• Yarv-dev / Yarv-devel ML 参加者の皆様• NaCl  まつもとゆきひろさん• 筑波大 前田敦司助教授•産総研 首藤一幸さん• And others

• RHG 読書会参加者の皆様• Ruby Hackers

Page 49: Ruby プログラムを高速に 実行するための処理系の開発

49

Page 50: Ruby プログラムを高速に 実行するための処理系の開発

50

Page 51: Ruby プログラムを高速に 実行するための処理系の開発

51

YARV での最適化 (cont.) インラインキャッシュ (cont.)

• Global “VM state counter”

• VM state counter は再定義時インクリメント• メソッド再定義• 定数再定義

• インラインキャッシュ時,このカウンタ値を一緒に格納

• キャッシュ参照時,現在のカウンタ値と格納されたカウンタ値を比較•同じならキャッシュヒット•違ったらキャッシュミス

Page 52: Ruby プログラムを高速に 実行するための処理系の開発

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

Page 53: Ruby プログラムを高速に 実行するための処理系の開発

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;}

Page 54: Ruby プログラムを高速に 実行するための処理系の開発

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))

Page 55: Ruby プログラムを高速に 実行するための処理系の開発

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

Page 56: Ruby プログラムを高速に 実行するための処理系の開発

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)

Page 57: Ruby プログラムを高速に 実行するための処理系の開発

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)

Page 58: Ruby プログラムを高速に 実行するための処理系の開発

58

命令記述フォーマット (cont.)

DEFINE_INSNputobject # 命令名(VALUE obj) # オペランド() # スタックから取る(VALUE val) # スタックへ置く{ val = obj; /* C 言語で実装を記述 */}

Page 59: Ruby プログラムを高速に 実行するための処理系の開発

59

VM 概要 – Stack Frames Method Frame

• 他の VM とほぼ同様• クラスフレームもほぼ同様

Control frame• 各フレームが持つ• レシーバ• 命令列情報• 継続情報( caller regs )• CFP によりアクセス可

Stack

Control

ArgsLocals

LFP, DFP

SP

Environment

CFP Self

ISeq

Cont.

Block

Page 60: Ruby プログラムを高速に 実行するための処理系の開発

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

……

Page 61: Ruby プログラムを高速に 実行するための処理系の開発

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;