how did yarv2llvm fail

37
How Did Yarv2llvm Fail yarv2llvm はどう失敗したのか CSNagoya 三浦英樹 RubyKaigi2010

Upload: miura1729

Post on 18-Jul-2015

706 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: How did yarv2llvm fail

How Did Yarv2llvm Failyarv2llvm はどう失敗したのか

CSNagoya

三浦英樹

RubyKaigi2010

Page 2: How did yarv2llvm fail

発表の流れ (Agenta)

• 速度低下の要因

• Yarv2llvm を紹介します

• Yarv2llvm を Dis ります

• ytl の紹介します

• まとめ

• ( 質問で )ytl が Dis られます

Page 3: How did yarv2llvm fail

速度低下の要因(Resason of slowdown)

• Dynamic Method Search

a + b

• BOXING/UNBOXING

Fixnum#+ ?

Float#+ ?

String#+ ?

Float

1.0

a a

BOXING

a 1.0

UNBOXING

Page 4: How did yarv2llvm fail

速度低下の要因(Resason of slowdown)

• Dynamic Method Search

a + b

• BOXING/UNBOXING

Fixnum#+ ?

Float#+ ?

String#+ ?

Float

1.0

a a

BOXING

a 1.0

UNBOXING

どちらも、扱っている型がコンパイル時分からないから支払うコスト

型推論で OK

Page 5: How did yarv2llvm fail

速度低下の要因(Resason of slowdown)

• Dynamic Method Search

a + b

• BOXING/UNBOXING

Fixnum#+ ?

Float#+ ?

String#+ ?

Float

1.0

a a

BOXING

a 1.0

UNBOXING

どちらも、扱っている型が分からないから支払うコスト

型推論で OKでも新たな問題が・・・

Page 6: How did yarv2llvm fail

yarv2llvm

• Ruby1.9 の VM(YARV と呼ばれていた )の命令列を LLVM に変換します

• Tom Bagby さんの llvmruby を使用していて、それ以外はすべて Ruby で記述してます

• 実行速度を上げるために型推論を行います

Page 7: How did yarv2llvm fail

型推論の概要 ( その 1)

  def   f (x)

x

  end

  f(3)

f: α  →  β

Page 8: How did yarv2llvm fail

型推論の概要 ( その 1)

  def   f (x)

x

  end

  f(3)

f: α  →  β

Fixnum

Page 9: How did yarv2llvm fail

型推論の概要 ( その 1)

  def   f (x)

x

  end

  f(3)

f: α  →  β

Fixnum

Fixnum

Fixnum

Fixnum Fixnum

Page 10: How did yarv2llvm fail

型推論の概要 ( その 2)

[1, 2, 3].map {|n| n * 2}

class Array

def map

   res.push yield self[i]

Block : α  → β

Page 11: How did yarv2llvm fail

デモ

• みんな大好きフィボナッチ

• SDL が使えます

• とっても高度な Hellow World

Page 12: How did yarv2llvm fail

m = LLVM::Module.new('hello') LLVM::ExecutionEngine.get(m)p_char = Type.pointer(Type::Int8Ty)ftype = Type.function(Type::Int32Ty, [p_char]) ftype = ftype.to_rawprintf = m.external_function('printf', ftype) ftype = Type.function(Type::Int32Ty, []) ftype = ftype.to_rawmain = m.get_or_insert_function('main', ftype) b = main.create_block.builderstrptr = b.create_global_string_ptr("Hello World! \n")b.call(printf, strptr)b.return(strptr)LLVM::ExecutionEngine.run_function(main)

Page 13: How did yarv2llvm fail

Yarv2llvm を Dis ります

• おもったより速くないんだよね

• 出来そうで、出来ない。すこしバグってる型推論

• Ruby フルセット? 美味しいのそれ?

Page 14: How did yarv2llvm fail

0

1

2

3

4

5

6

7

8

9

10

11

12

Rate Ruby1.9 / Yarv2llvm

Page 15: How did yarv2llvm fail

なぜ遅いのか

• ランタイム ( 文字列処理や GC) は Ruby1.9 と同じ処理

• BOXING が必要– Ruby1.9 のランタイムを呼ぶために→ GC ができない

– 中には余分な BOXING で Ruby1.9 より遅くなる場合も

– 解決策はランタイムを GC を含めて作りなおすこと

Ruby そのものでランタイムを書くのが良い

Page 16: How did yarv2llvm fail

ランタイムを Ruby で書く場合

あらかじめコンパイルしておいてリンクすればよいというわけではない

型推論の結果による Specializationが前提– 例えば

   [1, 7, 2, 9].sort → 比較処理を整数型限定にすることで BOXING を避ける

コンパイル時間の増大

Page 17: How did yarv2llvm fail

型推論出来ないプログラム

• それは bm_app_pentomino.rb から始まった

def bar(arr)

arr.map {|n| n * 2}

end

def foo(arr)

bar(arr.map {|n| n * 2})

end

p foo(["1", "2", "3"])

Page 18: How did yarv2llvm fail

型推論できないプログラム

• それは bm_app_pentomino.rb から始まった

この型が決まらないと

def bar(arr)

arr.map {|n| n * 2}

end

def foo(arr)

bar(arr.map {|n| n * 2})

end

p foo(["1", "2", "3"])

Page 19: How did yarv2llvm fail

型推論出来ないプログラム

• それは bm_app_pentomino.rb から始まった

このメソッドが何なのか決定できない

def bar(arr)

arr.map {|n| n * 2}

end

def foo(arr)

bar(arr.map {|n| n * 2})

end

p foo(["1", "2", "3"])

Page 20: How did yarv2llvm fail

型推論出来ないプログラム

• このバグからいろいろ崩壊

– 型推論が 1 ステップでは出来ず、型推論の実行時間が増大する

• All pure ruby では荷が重くなってきた

– そもそもプログラムのメンテナンス性が悪すぎてこのバグが解決できなかった

• 直そうとすると別のところが動かなくなる

• LLVM コード生成と型推論が混然一体となっている

• クロージャーの乱用

Page 21: How did yarv2llvm fail

Yarv2llvm で実装出来ない機能

• Send• 引数が定数ではない require( 動的 require)• Eval• 動的再定義

• Bignumいわゆる、黒魔術 (Bignum は除く )そもそも、実現する必要があるのか?

  ➭ こういうのを実現しないと Ruby の処理系と認めてくれない

Page 22: How did yarv2llvm fail

Yarv2llvm で実装出来ない機能

黒魔術は何が問題か → 

型推論の結果をくつがえしてしまう

想定しない型のデータがやってきてバグる

  a = 10

eval(“a = ‘foo’”)  print (a * 10)   # Fixnum の掛け算命令を生成すると

 ?

Page 23: How did yarv2llvm fail

Yarv2llvm で実装出来ない機能

ではどうするのか?

  新しい型情報で型推論とコンパイルをし直す  

    Compile and type inference with new type inforamation

  コードだけではなく、スタックフレームやオブジェクトも新しい型情報に合わせて書き換える必要がある場合もある

     on-stack replacement

Page 24: How did yarv2llvm fail

実現の難しさ

Easy    Send ↑ 動的メソッド定義、動的 require 

↓ Eavl( 型変更なし ) Hard Bignum , Eval( 型変更あり )

Bignum が最も難しい

・ 実行中のメソッドも書き換える必要がある

• スタックフレーム中のデータも書き換える必要がある

• 比較的利用頻度が多いので遅くするわけにはいかない

a = 1

eval(“a = 1.3”)

c = a + 3

Page 25: How did yarv2llvm fail

Ytl の紹介

• フルセットを目標に開発しているトランスレータ

• 独自のネイティブコード生成ライブラリ(ytljit) を構築– フルセット Ruby+ 型推論をサポートするため、個性的な ( 癖のある ) 仕様

– アセンブラベース

– X86 ネイティブコード (32/64bit) を直接生成• マクロ・疑似命令が ABI を含めて 32/64bit の違いを吸収

– LLVM はもう使わない

Page 26: How did yarv2llvm fail

言い訳

• Ytl は未完成です– 簡単な Ruby プログラムが型推論 * 無し * でコンパイル出来る程度です。

• Ytl は yarv2llvm より ** 遅い ** です– 数値計算を多用するプログラムの場合

– LLVM は化け物です。あの最適化とは勝負できません

– LLVM 相当の最適化が可能になった時 yarv2llvmより確実に速くなるでしょう

Page 27: How did yarv2llvm fail

Structure of ytl

VM

Translator

Type inference M echanism

YARV C ode

Assem bler

C ode Space

Type inference Policy

ytljit

M arshal for VM and C odeSpace

Page 28: How did yarv2llvm fail

Code Space• 実行可能なネイティブコードを格納する

• コード書き換えを前提としたデータ構造

• コード書き換え後も参照関係を自動的に維持する

add eax, 1

jz Foo

mov eax, 0

Foo

add eax, 1

add eax, 1

jz Foo

mov eax, 0

jmp Foo

Foo

sub eax, 1

コードを 1 つの領域にまとめる

書き換えると全部書き換えないといけない

基本ブロック毎に分割

参照関係を自動的に更新

Foo

check bignum

call _bignum_sub

realloc自動的に更新

Page 29: How did yarv2llvm fail

VM• Ruby プログラムの中間表現

– コンパイル時だけではなく、実行時も保持

– ネイティブコードを生成して実行

• 型推論を行うことができる– ネイティブコード生成時には型推論を行う

– 再コンパイル時には必要な制約をつけて型推論をやり直すことができる

– 型推論は ytl で SelfCompile したネイティブコードで実行

• 構造はシーケンスではなく、グラフ構造– 型推論をやり易くするため

• Marshal 可能 ( 型推論ルーチンも含めて )

Page 30: How did yarv2llvm fail

実行の流れ

YARV

VM( 型無し )

VM( 型付 )

ASM

Native Code

putobject 1

putobject 1

send :+

Lit: 1 Lit: 1

Send :+

Lit: 1: Fixnum Lit: 1:Fixnum

Send :fixnum_+ : Fixnum

mov eax, 1

add eax, 1

0101110001111

変換

型推論

コンパイル

アセンブル

再コンパイル

Page 31: How did yarv2llvm fail

Yarv2llvm での課題の解決策( その1)

• ランタイムを Ruby で書くことによるコンパイル時間の増大– VM を Marshal 可能にする

– VM レベルで型推論を行うので、型推論によるSpecialization は可能

• 型推論の複雑化と推論時間の増大– コード生成と型推論を分ける

– 型推論処理を Self Compile してネイティブコードで実行する

Page 32: How did yarv2llvm fail

Yarv2llvm での課題の解決策( その 2 )

• Bignum をはじめとする実装できない機能– これらを実装するため動的再コンパイルを採用

  → デモの Hello World はこのための実験だった

– 実行時のスタックフレームの構造を完全に把握するためコード生成はアセンブラレベルで行う 

  →  LLVM ではバージョン・オプティマイズによりスタックフレームの構造が変わる可能性大

– 効率よく動的再コンパイルを実現するため、 Code Space に工夫

Page 33: How did yarv2llvm fail

まとめ

• Yarv2llvm では主に3つ問題がある– 速度 (CRuby ランタイム使用時の BOXING)– 不完全で遅い型推論

– eval, bignum 等実現できない仕様がある

• 問題を解決するために ytl を開発中である– ランタイムを Ruby で記述できるように Marshal 可能にする

– 型推論の Native Code 化– On-Stack Replacement とそれを支援する仕組み

Page 34: How did yarv2llvm fail

ご清聴ありがとうございます

Page 35: How did yarv2llvm fail

BigNum の実現方法 a = a + b

overflow

対応する VM のノードを調べる

  a の型を bignum に変更

 型推論、コンパイルのやり直し

スタックフレームの書き換え

  restart にジャンプ ( 書き換え後のコードであることに注意 )

add a, b

jo overflow

restart:

:

Page 36: How did yarv2llvm fail

型推論の概要 ( その 3)

[1, “2”, 3].map {|n| n * 2}

class Array

def map

   res.push yield self[i]

Block : α  → β

Fixnum or String

Generate Dynamic Dispatch

Page 37: How did yarv2llvm fail

自己紹介

• Ruby と Lisp(! 声優 ) の好きな水道屋です– S式入力の下水道図面生成プログラムとか

• Blog (miura1729 の日記 ) http://d.hatena.ne.jp/miura1729

• Twitter @miura1729

• http://github.com/miura1729