Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Rubinius Under a Microscope

732 views

Published on

表参道.rb #8でLTしたRubiniusについてのスライドです。

Published in: Software
  • Be the first to comment

Rubinius Under a Microscope

  1. 1. Rubiniusのしくみ @highwide 表参道.rb #8
  2. 2. $whoami @highwide (Twitter / GitHub) (株)リブセンスでRailsを書いてます 最近は初めてのElasticsearchに四苦八苦しています 社内の「コンパイラ/インタプリタ」をテーマにしたLT大会で Rubiniusについて調べたので、表参道.rbでも披露させてもら おうと今日はやってきました よろしくお願いします!
  3. 3. Rubiniusって何?
  4. 4. Rubyの処理系の1つ CRuby: CによるRubyの実装
 (MRI: Matz Ruby Implementation) JRuby: JVMで動くRubyの実装 Rubinius: 作者: Evan Phoenixさん
 (RubyKaigi3日目Keynoteスピーカー) 高速なRuby実行環境を目指している バイトコード仮想マシンがC++で実装 LLVMによるJITコンパイルの仕組みがある コンパイラの大半がRubyで書かれている
  5. 5. Ruby6割 / C++3割
  6. 6. コンパイラの大半が Rubyで書かれている? どうやって動くの?
  7. 7. % cat hoge.rb 10.times do |n| puts "No. #{n}" end % ./rubinius/bin/rbx hoge.rb No. 0 No. 1 No. 2 No. 3 No. 4 No. 5 No. 6 No. 7 No. 8 No. 9 例えば: この裏で行われていることは?
  8. 8. 実行されるまでの流れ Rubinius命令列 Rubyコード トークン列 AST LLVM命令列 機械語 バイトコード コンパイル 必要に応じて JITコンパイル VM 実行 解釈
  9. 9. ちなみにMRI YARV命令列 Rubyコード トークン列 AST バイトコード コンパイル YARV 実行 解釈
  10. 10. バイトコードコンパイルとは RubyのコードをVMが理解できる命令列(バイト コード)にコンパイルすること ざっくり言うと パーサがRubyのコードをバラして AST(抽象構文木)を作って ASTを命令列化して 命令列オブジェクトにして プロパティを設定して VMが読める「 .rbc」形式にします
  11. 11. バイトコードコンパイルの各段階を 「ステージ」と呼ぶそうです http://rubinius.com/doc/ja/bytecode-compiler/
  12. 12. パーサがRubyのコードをバラして (どう見てもMRIのパーサ)
  13. 13. ASTを作って (コンパイル時に -Aオプションで ASTを見られる) % ./rubinius/bin/rbx compile -A hoge.rb @pre_exe: [] @name: :__script__ @file: "hoge.rb" @body: Send @line: 1 @name: :times @privately: false @vcall_style: false @receiver: FixnumLiteral @line: 1 @value: 10 @block: Iter @line: 1 @locals: nil @body: SendWithArguments @line: 2 @name: :puts @privately: true @vcall_style: false @block: nil @receiver: Self @line: 2 ----snip----
  14. 14. ちょっと余談: コンパイル時に -SオプションでS式を見られる % ./rubinius/bin/rbx compile -S hoge.rb [:script, [:call, [:lit, 10], :times, [:arglist, [:iter, [:args, :n], [:call, nil, :puts, [:arglist, [:dstr, "No. ", [:evstr, [:lvar, :n]]]]]]]]]
  15. 15. % ./rubinius/bin/rbx compile -B hoge.rb ============= :__script__ ============== Arguments: 0 required, 0 post, 0 total Arity: 0 Locals: 0 Stack size: 2 Literals: 2: <compiled code>, :times Lines to IP: 1: 0..9 0000: push_int 10 0002: create_block #<Rubinius::CompiledCode __block__ file=hoge.rb> 0004: send_stack_with_block :times, 0 0007: pop 0008: push_true 0009: ret ---------------------------------------- ============== :__block__ ============== Arguments: 1 required, 0 post, 1 total Arity: 1 Locals: 1: n Stack size: 5 Literals: 3: "No. ", :to_s, :puts Line: 1 Lines to IP: 2: 0..14 0000: push_self 0001: push_literal "No. " 0003: push_local 0 0005: allow_private 0006: meta_to_s :to_s 0008: string_build 2 0010: allow_private 0011: send_stack :puts, 1 0014: ret ---------------------------------------- ASTを命令列化する (コンパイル時に -Bオプションで バイトコードを 見られる)
  16. 16. % ./rubinius/bin/rbx compile -B hoge.rb ============= :__script__ ============== Arguments: 0 required, 0 post, 0 total Arity: 0 Locals: 0 Stack size: 2 Literals: 2: <compiled code>, :times Lines to IP: 1: 0..9 0000: push_int 10 0002: create_block #<Rubinius::CompiledCode __block__ file=hoge.rb> 0004: send_stack_with_block :times, 0 0007: pop 0008: push_true 0009: ret def push_int(arg1) if arg1 > 2 and arg1 < 256 @stream << 4 << arg1 @current_block.add_stack(0, 1) @ip += 2 @instruction = 4 else case arg1 when -1 meta_push_neg_1 when 0 meta_push_0 when 1 meta_push_1 when 2 meta_push_2 else push_literal arg1 ----snip---- def send_stack_with_block(arg1, arg2) @stream << 57 << arg1 << arg2 @ip += 3 @current_block.add_stack(arg2+2, 1) @instruction = 57 end これらのバイトコードは、Rubyの DSLで表現されていて、仮想マシ ン(C++)の命令と対応している
  17. 17. % ./rubinius/bin/rbx compile -B hoge.rb ============= :__script__ ============== Arguments: 0 required, 0 post, 0 total Arity: 0 Locals: 0 Stack size: 2 Literals: 2: <compiled code>, :times Lines to IP: 1: 0..9 0000: push_int 10 0002: create_block #<Rubinius::CompiledCode __block__ file=hoge.rb> 0004: send_stack_with_block :times, 0 0007: pop 0008: push_true 0009: ret send_stack∼といった命令列に渡されるメソッド名は Rubiniusの中で、Rubyで定義されている ※組み込みクラスはC++で書かれているので一部C++で処理するものも。 def times return to_enum(:times) { self } unless block_given? i = 0 while i < self yield i i += 1 end self end rubinius/kernel/common/integer.rb バイトコード ※Rubiniusのビルド時に コンパイル済
  18. 18. 言語のkernelが Rubyで書かれている ことがわかります
  19. 19. 10.times do |n| puts "No. #{n}" end コンパイルされた命令列を C++で書かれた VMが評価していきます !RBIX 1933235666460458080 22 M 1 n n x E 8 US-ASCII 10 __script__ i 10 4 10 63 0 57 1 0 21 2 17 I 2 I 0 I 0 -------snip------ hoge.rb hoge.rbc 3行 158行
  20. 20. ここからが Rubiniusの真骨頂 JITコンパイルだー! と思うのですが...
  21. 21. 誤解しがちなこと
 (僕が誤解していたこと) 「なぜRubiniusはLLVMでコンパイルする言語なのに CやRustほど速くないの?」
  22. 22. "Rubinius doesn't compile Ruby down native code, instead it uses a JIT to compile certain blocks of code, but only whenever possible (or deemed necessary based on the call amounts)." "RubiniusはRubyを機械語にコンパイルせずに、代わり に、いくつかのブロックを可能なときだけ(あるいはそのブ ロックの呼び出し回数に応じた必要性を判断して)、JITコンパイ ルを行う" 誤解しがちなこと
 (僕が誤解していたこと) https://github.com/rubinius/rubinius/issues/3165
  23. 23. すべてのコードを JITコンパイルで 機械語にしているわけ じゃなかった
  24. 24. RubiniusのJITコンパイル 特徴: Method JIT > メソッドが何度も呼び出されるときに行なう最適化です。この方法だと, 一度コンパイルされたメソッドは再利用できますが,コンパイルには時間が かかり,インライン化によってメソッドのサイズが大きくなります。 http://gihyo.jp/news/report/01/rubykaigi2015/0003 命令1 命令1 命令2 命令2 命令1 命令1の
 定義 命令2の
 定義 インライン化 命令1の定義 命令1の定義 命令1の定義 命令2の定義 命令2の定義 LLVMが最適化しやすい 動的言語の性質を活かしたまま、静的な割当てが可能
  25. 25. おさらい Rubinius命令列 Rubyコード トークン列 AST LLVM命令列 機械語 C 必要に応じて JITコンパイル VM 実行 解釈 Ruby C++
  26. 26. カーネルがRubyで 書かれているということは ActiveSupportの メソッドの移植なんて こともできるのでは?
  27. 27. Rubyで 書かれているので さくっと 「try!」実装する なんてことも できました! class Object ----snip---- def try!(*a, &b) if a.empty? && block_given? if b.arity.zero? instance_eval(&b) else yield self end else public_send(*a, &b) end end end kernel/alpha.rb class NilClass ----snip---- def try!(*args) nil end end kernel/nil.rb % cat try_length.rb
 p 'hoge'.try!(:length) p nil.try!(:length) % ./bin/rbx try_length.rb
 4 nil 「kernel/」にRubyのメソッドの定義 が書かれているので、Objectクラスと nilクラスにtry!を定義してコンパイル
  28. 28. 本当はRubiniusに ぼっちオペレーター 実装しようとしたのですが parse.yが書けずに断念しました &.
  29. 29. Rubinius 思った以上に おもしろかったです!
  30. 30. "Rubinius はただの Ruby 処理系では ない。Rubinius は Ruby コ ミュニティ にとって価値ある学習リソースだ " 「Rubyのしくみ」p330
  31. 31. ありがとう ございました
  32. 32. 「お詫び」または
 「Thank you for your マサカリ」 Rubiniusのコードを完全に追えたわけではなく、書 籍やドキュメント等の情報を元にしながらポイント ごとに実装を見て、まとめました...。 おかしいなと思ったら是非ご指摘くださいm(_ _)m
  33. 33. 謝辞 社内でLTをやったあと、「Rubyのしくみ」 の原文や、Rubiniusのissueから、「すべて のコードを機械語にJITコンパイルするわけ ではない」ということを突き止めて説明して くれた、同僚@na_o_ys氏にとっても感謝し ています!!
  34. 34. Web https://github.com/rubinius/ http://rubinius.com/doc/ja/what-is-rubinius/
 - Rubinius.com 「Rubiniusとは」 https://blog.engineyard.com/2010/making-ruby-fast-the-rubinius-jit
 - ENGINE YARD BLOG 「Making Ruby Fast: The Rubinius JIT」 http://gihyo.jp/news/report/01/rubykaigi2015/0003
 - gihyo.jp 「RubyKaigi 2015レポート Evan Phoenixさん「Rubyを速く するのは今がその時」 ∼RubyKaigi 2015 基調講演 3日目」 図書 Rubyのしくみ -Ruby Under a Microscope-
 - Pat Shaughnessy (著), 島田 浩二 (翻訳), 角谷 信太郎 (翻訳) 参考

×