君たちの愛したmruby-profilerは復活した(その3)

では、どうやってIREP構造体とprof_irep構造体を結びつけたのでしょうか?この説明の前にIREP構造体とは何かを思い出してみましょう。IREP構造体はVMの命令の一塊をVMが扱いやすいように補助的な情報とともにまとめたものでした。IREP構造体はメソッドとかブロックのVMが扱いやすい形式ともいえます。こういうことから、あるcode_fetch_hookの呼び出しとその前のcode_fetch_hookで渡ってくるIREP構造体が違う場合、次のような原因が考えられます。

  • メソッドやyieldの呼び出し
  • メソッドやブロックからのreturn
  • その他(例外とかFiberとか)

つまり、code_fetch_hookにあるIREP構造体(ここではAとします)が渡されたと分かっている時、次に渡ってくるIREP構造体は大体絞られるわけです。つまり、A中で呼び出しているメソッドのIREP構造体か、Aを呼び出したIREP構造体です。また、Rubyの場合あるメソッドがどのメソッドを呼び出すかは変なことをしなければ、大体静的に決まります。

そんな感じで次のような感じで結びつけます。ちょうどコールグラフを作る感じと言うとイメージしやすいかもしれません。

  • prof_irep構造体に次のようなメンバーを加える。prof_irepはmruby-profiler側で定義するのでメンバーの増減は自由自在です。
    • prof_irepに対応するIREP構造体(irep)
    • 現在のIREP構造体(メソッドやブロック)が呼び出したメソッドやブロックのprof_irep構造体(child)
    • 現在のIREP構造体を呼び出したメソッドやブロックのprof_irep構造体(parent)
  • 前回のcode_fetch_hookを呼び出したときのprof_irepを覚えておくcurrent_prof_irep変数(グローバル変数(笑))を用意する。
  • code_fetch_hook中ではcurrent_prof_irepに対応するIREP構造体と引数で渡ってきたIREP構造体が違うか調べる。同じなら問題ない。違うならprof_irep構造体を探さないといけない。探す手順はこんな感じ
    • current_prof_irepのchildメンバーの配列に入っているprof_irep構造体を調べる。もしchild中のprof_irep構造体が探しているIREP構造体に対応するものなら、current_prof_irepをそのprof_irep構造体にして検索終わり
    • childメンバーになければ、parentメンバーに入っているprof_irep構造体を同様に調べる。なければさらにpraentを辿り呼び出し履歴の最後まで調べる。見つかれば、childの時と同じでcurrent_prof_irepに設定する。
    • 見つからなければ、新たにprof_irep構造体を作り、current_prof_irepのchildの配列に加える

いろんな疑問点もあるでしょう。想定問答集です。

  • 1つのメソッドがたくさんメソッドを呼び出すと結局遅くならない?

  でっかいメソッドを作る方が悪い

  • この方法だとあるIREP構造体に対応するprof_irep構造体がたくさんできない?

  はい出来ます。ただし、無限に出来ることはないはずです。最後に結果を表示するときに集計する必要があるでしょう。逆にgprofのようなコールパスごとの実行時間も得られるはずです。まだ実装していませんが。

  • IREP構造体がGCされちゃったら大丈夫?

  うっ!それは・・・

  • 勘のいいガキは嫌い?

  はい!

そういうわけで、IREP構造体がGCされるとまずいのでIREP構造体中の参照カウンタをいじってGCされないようにしています。mruby-profilerからだとIREP構造体がGCされたかわかる方法が無いので仕方が無いですね。ファイナライザーがあればいいのですが 

と、最後はいいわけになってしまいましたが、無事終わりました。それではごきげんよー