Inside yarv2llvm(その5)

えらく更新が遅くなりました。今回は3回くらい書いている途中で消してしまい(戻るボタンを押したりして)、悲しい思いをしていました。結局、エディタで書いています。

イテレータの説明をすると予告したのですが、イテレータを説明するには変数の説明をしないといけないことに気づき、変数の説明を書くことにします。
ところで、変数アクセスの性能は重要です。ちょっと考えると普通メソッドコールより数が多いのだから、全体の性能にも大きく係ってくるなと判るのですが、どうもメソッドコールの性能ばっかり気になって変数アクセスの性能のことをないがしろにしてしまいます。でも、load/store一発のローカル変数だけを使ったfibではRuby1.9に比べて30倍以上のスピードが出ますが、アクセスにハッシュ検索が必要なインスタンス変数を多用したnbodyでは5倍くらいのスピードしか出ません。
こんなことから、変数アクセスは遅いとほかがいくら速くても遅いままということが判ります。でも、変数アクセスなんてどれも同じでしょ? って思われる方。いえいえ、そんなことはございません・・・、と広告なんかは書くのでしょうが、これは広告じゃないので本当のことを書くとyarv2llvmはたいしたことはやっていません。でも、ぜんぜん何の工夫も無いわけじゃないのでそれを書いていきます。

Rubyにはさまざまな種類の変数があります。

今回はその中でローカル変数とダイナミックローカル変数について説明します。
ローカル変数とダイナミックローカル変数の違いはローカル変数はメソッド内全体でアクセスできる変数、ダイナミックローカル変数はブロック内のみアクセスできる変数です。この2つの変数はYARVでは別実装ですが、yarv2llvmでは同じ実現方法です。これからは、ローカル変数とダイナミックローカル変数をまとめてローカル変数と呼びます。
そのほかの変数は現在のところ、RubyAPIをほぼそのまま使っています*1

ローカル変数とダイナミックローカル変数はLLVMのalloca命令で返される領域(スタックフレーム)に格納されます。allocaを使うにあたって次のような注意が必要です*2

  • allocaをループの中で使うとセグメンテーションフォルトが発生することがあります。それも、すぐには発生せずかなりループを回った後で発生するので、GCバグか?って頭を抱えます。allocaは関数(LLVMの)の先頭でまとめて行うのが吉です。
  • allocaを連続して使っても連続したアドレスが返ってくる保証は無いです。しかも、最適化をしないと連続していて、最適化をすると連続しないのが普通です。

連続していないとは例えば、

 a = b.alloca(Int32Ty, 1)
 b = b.alloca(Int32Ty, 1)

としても、aとbは連続した領域に取られないかも知れないということです。単に、スタックポインタを増減させる命令だと思うとはまります。

yarv2llvmではローカル変数が連続した領域で取られることを保障することが必要だったので、次のようなことをしています。

  1. メソッドで使用するすべてのローカル変数をメンバーとする構造体を定義する
  2. 1で定義した構造体の実体をallocaで確保する
  3. 構造体の個々のメンバー(個々のローカル変数)のアドレスをレジスタに入れておく。

3でアドレスを計算しておくのはいちいち変数アクセスのたびに構造体のオフセット計算をしていると遅そうと思ったからです。でも、最適化を掛ければいちいちオフセット計算なんかしないから無意味な気がします。それでも、開発中は生成される最適化前のllvmのbitコードがいちいちオフセット計算をせずにすっきりするので良かったかなと思っています。

なんでわざわざ構造体なんかを定義しているのか?連続した領域であることを保障する必要とは? 次回のイテレータの心だー

つづく・・・、けどまた遅れるかも・・・

*1:ただし、一部検索結果をキャッシュしたりしています。

*2:ということは失敗したということですね