rubykaigi2014: just in time compiler for cruby

27
A Just In Time Compiler for CRuby CRuby言語処理系向けJITコンパイラ Masahiro Ide Yokohama Na=onal University RubyKaigi 2014 2014919() 1

Upload: imasahiro

Post on 28-Nov-2014

258 views

Category:

Technology


1 download

DESCRIPTION

RubyKaigi2014: Just in Time compiler for CRuby http://rubykaigi.org/2014/presentation/S-MasahiroIde

TRANSCRIPT

Page 1: RubyKaigi2014: Just in Time compiler for CRuby

A  Just  In  Time  Compiler  for  CRuby  CRuby言語処理系向けJITコンパイラ

Masahiro  Ide  Yokohama  Na=onal  University  

RubyKaigi  2014  2014年9月19日(金)  

1

Page 2: RubyKaigi2014: Just in Time compiler for CRuby

Outline

•  Ruby  •  Just-­‐In-­‐Time(JIT)  compiler  •  RuJIT:  a  JIT  compiler  for  Ruby  

※あまり実装よりの内容ではありません          また,CRubyの実装に詳しくない人でも分かる          内容(にしたつもり)です.

2

Page 3: RubyKaigi2014: Just in Time compiler for CRuby

Ruby  Language  and  Implementa=on

•  Ruby  is  a  dynamically  typed  scrip=ng  language  – Support  excep=on,  garbage  collec=on,  con=nua=on  – CRuby  is  Ruby  interpreter  in  C  (de  facto  standard  implementa=on  of  Ruby)  

– Many  Implementa=ons  JRuby(Java),  IRonRuby(.NET),  Rubinius(Ruby),  MRuby(C),  Topaz(PyPy)  

– Numeric  benchmarks  10-­‐100  =mes  slower  than  C  •  Need  to  improve!!!  

3

Page 4: RubyKaigi2014: Just in Time compiler for CRuby

CRuby  Internal

#toplevel  09  putobject  100  11  send  :=mes,  block  17  leave  

#block  02  putself  03  getdynamic  i  06  send  :puts,  nil  14  leave  

YARV  bytecode  

Ruby  code  100.=mes  do  |i|    puts    i  end  

Parser

               AST

Bytecode  Compiler

Interpreter C  library

Memory  Manager

CPU 4

Page 5: RubyKaigi2014: Just in Time compiler for CRuby

HOW  TO  SPEEDUP  RUBY

1.  Translate/Compile  to  low-­‐level  language  – プログラム実行中にコンパイル→  Just-­‐In-­‐Time(JIT)  – プログラム実行前にコンパイル→Ahead-­‐Of-­‐Time(AOT)  –  Target  

•  Java  bytecode  (JRuby),  .NET  CLR  (IronRuby),  LLVM  IR(Rubinius)  

2.  Speedup  Interpreter  – ディスパッチ方法の工夫  – メソッドキャッシュ  – …  

5

Page 6: RubyKaigi2014: Just in Time compiler for CRuby

Outline

•  Ruby  •  Just-­‐In-­‐Time(JIT)  compiler  •  RuJIT:  a  JIT  compiler  for  Ruby  – Design  – Usage  – Current  status  

 

6

Page 7: RubyKaigi2014: Just in Time compiler for CRuby

RuJIT

•  A  Trace  based  JIT  compiler  for  CRuby  – Like  Firefox’s  javascript  VM  approach  

•  Based  on  current  version  of  CRuby  •  Started  in  April  2014  •  More  Speed,  Speed,  Speed  – …  but  maintain  compa=bility  with  current  Ruby

7

Page 8: RubyKaigi2014: Just in Time compiler for CRuby

Implementa=on  Strategies

•  Objec=ve  –  Improve  Ruby’s  performance  

•  Development  policy  –  Low  development  cost  

•  1  person,  2~3  month  – High  extensibility  

•  Easy  to  develop  new  op=miza=on  techniques  – 実用的なソフトウェア  

•  いつかRubyのメインラインに  →この方針に基づいて設計を行っている  

8

Page 9: RubyKaigi2014: Just in Time compiler for CRuby

RuJIT  overview

#toplevel  09  putobject  100  11  send  :=mes,  block  17  leave  

#block  02  putself  03  getdynamic  i  06  send  :puts,  nil  14  leave  

YARV  bytecode  

Ruby  code  100.=mes  do  |i|    puts    i  end  

Parser

               AST

Bytecode  Compiler

Interpreter C  library

Memory  Manager

CPU

RuJIT Na=ve  code

9

Page 10: RubyKaigi2014: Just in Time compiler for CRuby

RuJIT  Design

•  Trace  based  JIT  compiler  – Detect  hot  path(e.g.  loop)  

•  Intermediate  Representa=on    (IR)  for  op=miza=on  – Low  level  – Contain  run=me  informa=on  

•  Na=ve  Code  translator  – Use  C  compiler  (GCC,  clang)  as  backend  

10

YARV  

Trace  Selec=on  Engine Trace  Cache

RuJIT  Run=me

CRuby  Run=me

Op=mizer

IR  Generator

invoke

Code  Generator

C  Compiler

Na=ve  code

Trace compile

Page 11: RubyKaigi2014: Just in Time compiler for CRuby

トレース方式コンパイラの実行の流れ

11

def square(x) ! return x * x !end!!i = 0; y = 0 !while y < 100000! y += square(i) ! i = i + 1 !end

Page 12: RubyKaigi2014: Just in Time compiler for CRuby

Construct  trace def square(x) ! return x * x !end!!i = 0; y = 0 !while y < 100000! y += square(i) ! i = i + 1 !end

12

BB0:    guard_method_redefine(Fixnum.*)    guard_method_redefine(Fixnum.+)    guard_method_redefine(Fixnum.<)  

BB1:    guard_is_fixnum(i)    push_frame  #  invoke  square    guard_method_redefine(Fixnum.*)    tmp  =  fixnum_mul(i,  i)    pop_frame    #  leave  square    guard_method_redefine(Fixnum.+)    y  =  fixnum_plus(y,  tmp)    guard_method_redefine(Fixnum.+)    i  =  fixnum_plus(i,  1)    guard_method_redefine(Fixnum.<)    tmp2  =  fixnum_lt(i,  100000)    guard_nil(tmp2)    jump  BB1  

トレース

Page 13: RubyKaigi2014: Just in Time compiler for CRuby

Op=mize  trace def square(x) ! return x * x !end!!i = 0; y = 0 !while y < 100000! y += square(i) ! i = i + 1 !end

13

トレース

BB0:    guard_method_redefine(Fixnum.*)    guard_method_redefine(Fixnum.+)    guard_method_redefine(Fixnum.<)  

BB1:    guard_is_fixnum(i)    push_frame  #  invoke  square    guard_method_redefine(Fixnum.*)    tmp  =  fixnum_mul(i,  i)    pop_frame    #  leave  square    guard_method_redefine(Fixnum.+)    y  =  fixnum_plus(y,  tmp)    guard_method_redefine(Fixnum.+)    i  =  fixnum_plus(i,  1)    guard_method_redefine(Fixnum.<)    tmp2  =  fixnum_lt(i,  100000)    guard_nil(tmp2)    jump  BB1  

RuJIT  can  hoist  guard_method_redefine  

Page 14: RubyKaigi2014: Just in Time compiler for CRuby

Op=mize  trace def square(x) ! return x * x !end!!i = 0; y = 0 !while y < 100000! y += square(i) ! i = i + 1 !end

14

トレース

BB0:    guard_method_redefine(Fixnum.*)    guard_method_redefine(Fixnum.+)    guard_method_redefine(Fixnum.<)  

BB1:    guard_is_fixnum(i)    push_frame    guard_method_redefine(Fixnum.*)    tmp  =  fixnum_mul(i,  i)    pop_frame    guard_method_redefine(Fixnum.+)    y  =  fixnum_plus(y,  tmp)    guard_method_redefine(Fixnum.+)    i  =  fixnum_plus(i,  1)    guard_method_redefine(Fixnum.<)    tmp2  =  fixnum_lt(i,  100000)    guard_nil(tmp2)    jump  BB1  

RuJIT  can  remove  method  frame  

Page 15: RubyKaigi2014: Just in Time compiler for CRuby

Op=mize  trace def square(x) ! return x * x !end!!i = 0; y = 0 !while y < 100000! y += square(i) ! i = i + 1 !end

15

トレース

BB0:    guard_method_redefine(Fixnum.*)    guard_method_redefine(Fixnum.+)    guard_method_redefine(Fixnum.<)    guard_is_fixnum(i)  

BB1:    guard_is_fixnum(i)    push_frame    guard_method_redefine(Fixnum.*)    tmp  =  fixnum_mul(i,  i)    pop_frame    guard_method_redefine(Fixnum.+)    y  =  fixnum_plus(y,  tmp)    guard_method_redefine(Fixnum.+)    i  =  fixnum_plus(i,  1)    guard_method_redefine(Fixnum.<)    tmp2  =  fixnum_lt(i,  100000)    guard_nil(tmp2)    jump  BB1  

RuJIT  can  hoist  guard_is_fixnum  Because  variable  ‘i’  is  always  fixnum  

Page 16: RubyKaigi2014: Just in Time compiler for CRuby

Op=mize  trace def square(x) ! return x * x !end!!i = 0; y = 0 !while y < 100000! y += square(i) ! i = i + 1 !end

16

Na=ve  code

BB0:    guard_method_redefine(Fixnum.*)    guard_method_redefine(Fixnum.+)    guard_method_redefine(Fixnum.<)    guard_is_fixnum(i)  

BB1:    tmp  =  fixnum_mul(i,  i)    y  =  fixnum_plus(y,  tmp)    i  =  fixnum_plus(i,  1)    tmp2  =  fixnum_lt(i,  100000)    guard_nil(tmp2)    jump  BB1  

トレース

Page 17: RubyKaigi2014: Just in Time compiler for CRuby

トレースからインタプリタへの移行

17

Invalidate  compiled  code  and  Fall  back  to  yarv  interpreter  

#toplevel  09  putobject  100  11  send  :=mes,  block  17  leave  

#block  02  putself  03  getdynamic  i  06  send  :puts,  nil  14  leave  

YARV  Bytecode  

Parser

               AST

Bytecode  Compiler

Interpreter

CRuby

BB0:    guard_method_redefine(Fixnum.*)    guard_method_redefine(Fixnum.+)    guard_method_redefine(Fixnum.<)    guard_is_fixnum(i)  

BB1:    tmp  =  fixnum_mul(i,  i)    y  =  fixnum_plus(y,  tmp)    i  =  fixnum_plus(i,  1)    tmp2  =  fixnum_lt(i,  100000)    guard_nil(tmp2)    jump  BB1  

Fixnum.*  is  redefined!  

Page 18: RubyKaigi2014: Just in Time compiler for CRuby

ここまでのまとめ  トレース方式JITコンパイラ

•  特徴  – 頻繁に実行されるパスのみをコンパイル  

• 実行時に収集した仮定,前提条件を含めてコンパイル  – 積極的な最適化が可能  

•  JITで生成された機械語ではトレースに含まれていないパスが実行されるとインタプリタへfall  back  

•  欠点  – 頻繁なトレース↔インタプリタ遷移は性能劣化を招く  • スタック,レジスタの書き戻しなど…  

18

Page 19: RubyKaigi2014: Just in Time compiler for CRuby

RuJITのコンパイル手法

•  YARV  Bytecode→  RuJIT  IR→  C  code→  Na=ve  code  – たいていの内部命令はバイトコード命令に対応 –  JIT内部にアセンブラ不要  

• 開発コスト低い

19

バイトコード  命令

getlocal  x  getlocal  y  plus  x,  y

Ruby  Program

x  +  y

内部命令

guard_int  x  guard_int  y  iadd  x,  y

C言語

if(is_int(x)){      if(is_int(y)){            ret  =  x  +  y;      }  else  {…}  }  else  {…}

機械語

movl        %edi,  %ecx  andl        %esi,  %ecx  xorl        %eax,  %eax  testb      $1,  %cl  je            LBB0_2  addl        %edi,  %esi  movl        %esi,  %eax  

内部命令に対するCコードテンプレートを用いて変換

gcc  clang

Page 20: RubyKaigi2014: Just in Time compiler for CRuby

バイトコードから  RuJIT内部命令への変換

•  データ変換の記述は煩雑  – 各バイトコードごとのにスタック操作,pc操作,  内部データの更新  

– 最適化しやすいように内部命令をどんどん追加  

•  問題:どのように開発コストを抑えつつバグの                        少ない変換を行うか  →変換規則,内部命令仕様から変換コード自動生成  •  共通の処理は手で書かない  

20

Page 21: RubyKaigi2014: Just in Time compiler for CRuby

•  生成例  

内部命令記述と生成例

•  内部命令の変換則  

21

define FixnumAdd!:def :user!(LHS:lir_t, RHS:lir_t) !!emit FixnumAdd if! opcode is opt_plus and! method_name is "+" and ! LHS is Fixnum and ! RHS is Fixnum!!

if ( // rule0 FixnumAdd! (opcode == BIN(opt_plus)) && ! (ci->mid == idPLUS) && ! (ci->argc == 2) && ! (Fixnum.“+”は再定義されていない) && ! (FIXNUM_P(params[0])) && ! (FIXNUM_P(params[1])) !) { ! Emit_GuardMethod(idPLUS); ! Emit_GuardTypeFixnum(regs[0]); ! Emit_GuardTypeFixnum(regs[1]); ! Emit_FixnumAdd(regs[0], regs[1]); !}

Page 22: RubyKaigi2014: Just in Time compiler for CRuby

Op=miza=on

•  処理/実装コストの低いから,まずは実装  – Peephole  Op=miza=on  – Constant  Propaga=on  – Common  Sub-­‐expression  Elimina=on  – Dead  Code  Elimina=on  – Escape  analysis  +  Stack  alloca=on  

22

Page 23: RubyKaigi2014: Just in Time compiler for CRuby

Evalua=on

•  用いたベンチマーク  – CRuby処理系付属のベンチマーク(の一部)  

•  比較対象  – CRuby  (trunk)  

•  環境  – OSX  10.9  – Clang  3.5  

23

Page 24: RubyKaigi2014: Just in Time compiler for CRuby

Evalua=on

•  多くのベンチマークで2~5倍の高速化  •  いくつかのベンチマークは100倍以上

24

0  

1  

2  

3  

4  

5  

6  

7  

8  

9  

10   〜 〜 〜 〜 〜 〜 〜 〜 〜 〜 〜 〜 〜 〜

Page 25: RubyKaigi2014: Just in Time compiler for CRuby

Evalua=on •  変化なし・低速化のケース  –  トレース検出の変更  –  Cで書かれたコードはトレースできない

25

0  

0.2  

0.4  

0.6  

0.8  

1  

1.2  

io_file_create   io_file_read   io_file_write   io_select   io_select2   io_select3   app_factorial   app_fib  

Cで定義されているコード

トレースの検出対応できていないケース

Page 26: RubyKaigi2014: Just in Time compiler for CRuby

Conclusion

•  RuJIT  :  a  trace  based  JIT  compiler  for  CRuby  – トレース方式JITコンパイラによりRubyスクリプトのうち頻繁に実行される箇所を機械語へ変換  

– How  to  use  RuJIT  1.  git  clone  [email protected]:imasahiro/rujit.git  2.  cd  /path/to/rujit  3.  ./configure  &&  make  &&  make  install  4.  ruby  hello_world.rb  

•  今後の課題  – 互換性(Trace  API,  Excep=on,  Thread対応)  – Need  more  test  

26

Page 27: RubyKaigi2014: Just in Time compiler for CRuby

FAQ

•  Railsは動く?(もしくは他のrubyプログラムは?)  – まだ動きません.現在対応中です  

•  RuJIT,なんか動きません  – GithubのIssueに投げてもらえると助かります  – h~ps://github.com/imasahiro/rujit/issues  

•  ◯◯◯との比較は?  –  JRuby,  Rubinius,  Topaz,  mrubyのJIT,  yarv2llvm,  CastOff…  

– 現在評価中です  

27