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

Preview:

DESCRIPTION

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

Citation preview

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;

Recommended