ベ...ベンチマークの為にやっているんじゃないからねっ!

タイトルは大嘘でベンチマークの為に、トップレベルで定義されたメソッドはselfを渡さないようにしました。これでちょっとベンチマークが速くなります。まあ、ベンチマークだけじゃなくてクラスを使わないプログラムだと恩恵があるのではないかなと思います。

      user     system      total        real
Ruby   14930352
 14.031000   0.000000  14.031000 ( 14.219000)
llvm   14930352
  0.360000   0.000000   0.360000 (  0.377000)

33.6倍 -> 38.97倍です。でも、もともとの42.4倍にはかないません。これは、ブロックを実現するためにフレームを構造体にしたためだと思います。ブロックを使うかどうかで場合わけして速度が上げられると思いますが、ここも場合わけするのはちょっとやりすぎだと思うのでこの辺であきらめます。

追記

ささださんのコメントの返事が長くなりそうなので、ここに書きます。読み返してみた気づいたのですが、説明しないといけないところが省かれていてわけわかんないですね。すみません。

このページの話はささださんのいう通り、レシーバーを渡す(selfを渡すと書いたのですが、紛らわしいので表現を変えます)必要があるか無いかは、yarv2llvmで想定しているRubyのレベルでは静的に解析できる という話です。eval/再定義なんかが入っても解析できるのかはわからないです。

具体例で示します。

  10.times {|n| print n}

といったプログラムで、メソッドtimesにはなんらかの方法で10というレシーバーを渡す必要があります。yarv2llvmでは隠れた引数を用意して渡すようにしています。一方、

def fib(n)
   ...

と、トップレベルで定義した場合、fibではまずselfを参照しませんし、参照があればselfをnil(またはmainオブジェクト)に置き換えてもいいわけです(フルセットRubyでこう言い切れる自信はないのですが・・・)。そうであれば、隠れた引数を削除してnだけを渡すようにするとスピードが上がるんじゃないかなって思ったのが、今回の変更です。スピードが上がるといっても1回の関数呼び出しで3〜5回くらいのメモリーアクセス分だけですが。fibのようにほとんど関数呼び出しだけっていうプログラムだと効果がありますが、普通はほとんど効果は無いと思います。
メソッド定義がトップレベルで行われているかどうかの判定はlexicalに行えます。YARVのISeqはプログラムのlexicalな構造を保存していてくれるのでとても助かります。

あと、この最適化を行うにはメソッド呼び出しのコンパイル時にメソッドの情報があることが必要になります。そうするとコンパイラは2パス以上必要になりますし、autoloadなんかが出てくると不可能になります。