最初のベンチマーク
これまでに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コンパイラならではの最適化をかけなくてはいけません。
というわけで、今後は最適化を順次実装していくことにします。