最初のベンチマーク
これまでにif、while、メソッド呼び出し、ローカル変数のコンパイル処理を実装してきました。すでに簡単なプログラムが動くくらいになっていますので、この辺りでJITコンパイルされたコードの速度を確認してみたいと思います。
コードは以下の通りで、1から10000までの合計を計算する処理を100回繰り返すというものです。
# minirubyは$LOAD_PATHが空なので設定する $LOAD_PATH << File.expand_path("../../lib", File.dirname(__FILE__)) require "benchmark" # ベンチマーク対象のメソッド def m1 i = 1 sum = 0 while i <= 10000 sum += i i += 1 end sum end # JITコンパイル precompile Object, :m1 # ベンチマークを取る Benchmark.bm do |bm| bm.report("interpreted") { 100.times { m1_orig } } bm.report("precompiled") { 100.times { m1 } } end
precomipleでメソッドをコンパイルすると、元のメソッドのエイリアスが_origをというサフィックスで作成されますので、それとJITコンパイルされたメソッドの実行時間を比較しています。
なおマシンはIntel Core i7-2600 3.40Ghz、メモリ8GB、OSはWindows 7 SP1です。電源プランの設定で「高パフォーマンス」を選択しています(CPU性能を100%にする効果を期待)。結果は以下の通りです。
[~/work/rbjit/ruby/rbjit]$vc10/Release/miniruby examples/perf_while.rb user system total real interpreted 0.063000 0.000000 0.063000 ( 0.059003) precompiled 0.124000 0.000000 0.124000 ( 0.128008)
はい。JITコンパイルしたほうが遅いです。これは予想通りです。
これには以下の理由が考えられます。
- 整数演算中心のプログラムの場合、演算自体にほとんど時間がかからないため、実行時間の大半をメソッドの検索及び呼び出し処理が占める。
- MRIは、Fixnum#+のための専用のインストラクションを用意しており、Fixnum#+に関する検索及び呼び出し処理がとても速い。一方JITコンパイラは、Fixnum#+も通常のメソッドとして呼び出している。
素朴なJITコンパイルでは、YARVのインストラクションを辿る時間は節約できますが、その効果は実はたいしたことはありません。それだけでは、メソッド呼び出しを最適化したインタープリタに負けてしまうわけです。JITコンパイラが性能で上回るには、こちらもJITコンパイラならではの最適化をかけなくてはいけません。
というわけで、今後は最適化を順次実装していくことにします。
ローカル変数参照を実装しました
if文を実装しました
最初のJITコンパイラを実装しました
このバージョンのminirubyには、Object#precompileというメソッドが追加されています。これは
precompile <クラス>, <メソッド名(シンボル)>
という書式で呼び出すことで、<メソッド名>で指定されたメソッドの中身をコンパイルし、既存のメソッド定義を上書きします。既存のメソッド定義は<メソッド名>_origというエイリアスで残されます。
といっても今のところFixnumリテラルにしか対応していませんので、メソッドに書けるのはFixnumの範囲に収まる整数だけです。ひどい。
例えば
def m 1 end precompile Object, :m puts m # => 1
この例を見ても何がなんだかわかりませんが、デバッガでステップ実行すると、メソッドmがネイティブコードとして実行されていることがわかります。生成されたコードは以下のようなものでした。
mov eax, 3 ret
そのまんまですね。
いずれはJITコンパイルすべきメソッドをヒューリスティックに判定して自動コンパイルする機能をつけることになりますが、それをするにはコンパイルできる文法範囲が狭すぎますので、しばらくは上記の方法で指定することになります。
githubのURLは以下の通りです。10ファイル以上を一度にコミットしてます。
https://www.github/msumimz/ruby/tree/rbjit
ビルド方法は後で書きます。ビルドしても何もできませんが。
clangのアセンブリをIntel形式で出力する
clangで-Sオプションをつけてアセンブリを出力する場合、既定でAT&T形式になります。これをIntel形式にするには以下のオプションを使えばよいようです。
clang -S -o - -mllvm -x86-asm-syntax=intel test.c
'-mllvm'というのは、clangのインフラであるLLVMライブラリでサポートされているオプションを適用するための記法です。clangにはx86のアセンブラ構文を指定するオプションはありませんが、LLVMにはあるので、この記法で指定するわけです。ライブラリであるはずのLLVMにコマンドラインオプションが定義されているのはよくわかりませんが、そういうものなので仕方ありません。
ちなみに、このオプションをつけてもディレクティブはgas形式のままなので、MASM/NASMでは使えません。