msumimz's diary

RubyにJITコンパイラを実装する個人プロジェクトの情報発信ブログです。

最初のベンチマーク

これまでに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は、メソッド呼び出しごとに用意されたキャッシュ(いわゆるインライン・メソッド・キャッシュ)を実装しているが、JITコンパイラはグローバルキャッシュしか使っていない。
  • MRIは、Fixnum#+のための専用のインストラクションを用意しており、Fixnum#+に関する検索及び呼び出し処理がとても速い。一方JITコンパイラは、Fixnum#+も通常のメソッドとして呼び出している。

素朴なJITコンパイルでは、YARVのインストラクションを辿る時間は節約できますが、その効果は実はたいしたことはありません。それだけでは、メソッド呼び出しを最適化したインタープリタに負けてしまうわけです。JITコンパイラが性能で上回るには、こちらもJITコンパイラならではの最適化をかけなくてはいけません。

というわけで、今後は最適化を順次実装していくことにします。