Hatena::ブログ(Diary)

miura1729の日記 このページをアンテナに追加 RSSフィード

2014-08-20

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

20:26 |  君たちの愛したmruby-profilerは復活した(その3)を含むブックマーク  君たちの愛したmruby-profilerは復活した(その3)のブックマークコメント

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

つまり、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の配列に加える

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

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

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

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

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

  うっ!それは・・・

  • 勘のいいガキは嫌い?

  はい!

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

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

2014-08-19

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

19:48 |  君たちの愛したmruby-profilerは復活した(その2)を含むブックマーク  君たちの愛したmruby-profilerは復活した(その2)のブックマークコメント

昨日の続きで、irepメンバーとIREP構造体中のirepメンバーのインデックスがなぜ必要だったのかを説明します。

プロファイル情報はどこにあるの?

  プロファイラはかかった時間とか実行回数を(メソッド、行、命令)レベル(Profilerによる)で数えて後から分かりやすく表示するソフトです。ですので、どこかに数えた結果を持っていなければなりません。

mruby-profilerの場合はVMの命令ごとに実行時間を数えます。VMの命令はIREP構造体に格納されているので、命令毎の実行時間もIREP構造体に入れておくのが一番いいのですが、mruby本体に手を入れる必要があり、メモリ制約の厳しいmrubyにプロファイル用のメンバーを入れて貰うのは期待できません。

そこで、mruby-profilerはprof_irep構造体というプロファイルのための構造体をIREP構造体ごとに用意して、そこに実行時間などを格納するようにしました。この方法だと、問題が1つ残ります。IREP構造体とprof_irep構造体をどうやって結びつけるかです。

命令の実行時間を計測するために、code_fetch_hookの仕組みを使うのですが、code_fetch_hookは現在実行中のIREP構造体は引数としてもらえるのですが、それに対応するprof_irep構造体はなんとかIREP構造体から得る必要があります。

やりたいことは単なる連想配列なんですが、実行時に実行対象のIREP構造体はメソッドの呼び出しとかリターンとかで頻繁に変わりますし、VMの命令実行ごとに行われる処理なので、とにかく高速におこなうことが必要です。ハッシュテーブルを作るとかだとおそらくすごく遅くなるでしょう。

これまでの方法

 これはでは、mrb->irepがすべてのIREP構造体が入っている配列であることを利用して、mrb->irep配列と同じ長さのprof_irep配列を用意して、IREP構造体に対応するprof_irep構造体を格納します。こうすれば、prof_irep配列を経由して、IREP構造体からprof_irep構造体が結び付けられるわけです。

こんな感じのコードになります。

  idx = irep->idx                           /* IREP構造体のインデックス値を得る */
  current_prof_irep = prof_irep_array[idx]; /* IREP構造体に対応するprof_irep構造体を得る */

IREP構造体をGC出来ないことを除けば、かなり効率のよい方法です。irep->idxとかmrb->irepが無くなった今、別の方法を考えなければなりません。半年間いい方法が浮かばなかったのですが、ふとこの方法ほどではないのですが効率的な方法が浮かんだのでそれを実装しました。それが、今回の復活の決め手です。

それでは次回、ごきげんよーさようなら

2014-08-18

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

20:20 |  君たちの愛したmruby-profilerは復活した(その1)を含むブックマーク  君たちの愛したmruby-profilerは復活した(その1)のブックマークコメント

  お盆休みの一部を利用して、mruby-prpfiler(https://github.com/miura1729/mruby-profiler)を復活させました。

mruby-profilerって何?

mruby-profilerはVMの命令ごとに実行時間を数えていて、rubyソースコードVMの命令レベルで実行時間を表示してくれるというものです。フィボナッチを実行すると、こんな感じの出力が最後に出ます。


0000 0.00000 # Fib 39
0001 0.00000
0002 0.00000 def fib n
0003 4119.25760   return n if n < 2
             204668309 586.53534    MOVE
             204668309 666.29443    LOADI
             204668309 927.08844    LT
             204668309 1377.01244    JMPNOT
             102334155 562.32694    RETURN
0004 5941.06001   fib(n-2) + fib(n-1)
             102334154 301.43419    LOADSELF
             102334154 375.47193    MOVE
             102334154 472.92432    SUBI
             102334154 1341.11332    SEND
             102334154 318.84731    LOADSELF
             102334154 347.61494    MOVE
             102334154 446.54848    SUBI
             102334154 1346.05755    SEND
             102334154 364.15757    ADD
             102334154 626.89040    RETURN
0005 1139.70203 end
             204668310 1139.70185    ENTER
                     1 0.00008    LAMBDA
                     1 0.00010    METHOD
0006 0.00000
0007 0.00508 puts fib(39)
                     1 0.00003    LOADSELF
                     1 0.00000    LOADSELF
                     1 0.00002    LOADI
                     1 0.00015    SEND
                     1 0.00011    SEND
                     1 0.00477    STOP

1カラム目から始まる行がRubyレベルで、行番号、実行時間、Rubyレベルのソース。インデントされているのがVMレベルで、実行回数、実行時間、命令(オペコードとかは表示されない今後改善予定) です。

実行時間の単位はrdtscの出力をそのまま使っているのでよくわかりません。大きい数字なら時間がかかっているということで。

これを見れば、どこを直せば速くなるか一目瞭然ですね。そんな、甘くはないのですけど。

復活って?動かなかったの?

mruby-profilerは去年の10月に出来ていたのですが、今年の3月ごろに使えなくなりました。その理由はmrubyが変わってしまったため。もっと具体的に言うと、mrb_state構造体にirepというメンバーが無くなったためです。

irepメンバーはすべてのIREP構造体が入っている配列です。しかも、IREP構造体にはirepのどこに入っているかのインデックスがありました。IREP構造体は、mrubyのソースコードVM命令にコンパイルした結果が、リテラルテーブルとかシンボルテーブルなんかの実行に必要な情報とともにまとまっている構造体です。

なぜなくなったかと言うと、IREP構造体もGCの対象になるようにしたからです。irepメンバーにすべてのIREP構造体が入っていればどこにも指されないという条件は常に成り立たないわけです。irepメンバーが無ければ、必要のないIREPは死んでもらえるわけです(そう、花●とアンのように)。

ところが、mruby-profilerはirepメンバーとIREP構造体のirepメンバーのインデックスに大いに依存して作られていました(過去形)。

今日は炎天下の草取りで疲れたので、この辺で。ごきげんよう、またお会いしましょう。

2014-05-27

水道屋の僕が無人島に持っていきたい本

12:20 |  水道屋の僕が無人島に持っていきたい本を含むブックマーク  水道屋の僕が無人島に持っていきたい本のブックマークコメント

@matsumotoryさんの面白い記事 「インフラエンジニアの僕がキーボードのすぐ隣に置いておきたい本」 http://blog.matsumoto-r.jp/?p=4279 をぱくって記事を書いてみました。

手元に置いておきたいが、置いているわけじゃない5冊

Common Lisp 第二版 (http://www.amazon.co.jp/dp/4320025881)

一見、無味乾燥ですが面白い本です。「処理系の注」と「論理的根拠」のところをしっかり読みましょう。世の中には深く深くものを考える人がいるのだなと分かります。

初めてのRuby (http://www.amazon.co.jp/dp/4873113679)

プログラミング言語を使えるようになるだけならいろんな本もあるし、ネットでただで手に入れることもできるわけですが、言語のバックグラウンドにある思想も含めた優れた解説はなかなかお目にかかりません。

ハッカーのたのしみ (http://www.amazon.co.jp/dp/4434046683)

はっきりいってここで解説されている技法を仕事で使ったら、その仕事がコンパイラOSの開発じゃなければ9割がた怒られるでしょう。でも、プログラムの限界というのはそう自明じゃないことを教えてくれます。

Winnyの技術 (http://www.amazon.co.jp/dp/4756145485)

高速なシステムを作るのにこの本以上に役立つ本はなかなか無いでしょう。この作者の才能をつぶした京都府警は「ばかじゃないの?」としか言えないです。

素晴らしき数学世界 (http://www.amazon.co.jp/dp/4152093021)

進歩の速いコンピュータの世界(本当かな?)で長くとどまるには、コンピュータが扱う「数」というものに固定概念を持つべきではないと思います。この本はあらゆる角度から数に関する固定概念を崩してくれることでしょう。

 まとめ

当然ですが、ほかにも面白い本はたくさんあります。また、こんな記事が書けるほど素晴らしい本に出会いたいです。

2014-05-25

mruby-mmmの紹介

20:55 |  mruby-mmmの紹介を含むブックマーク  mruby-mmmの紹介のブックマークコメント

追記

Matzさんのアドバイス(https://twitter.com/yukihiro_matz/status/470578371789746176)でinclude MMMI, extend MMMCという煩雑なインタフェースはinclude MMMと簡単になりました。ありがとうございます。

本文

mrubyを使っていて、明らかに使わないと分かっているオブジェクトなのにGCで回収されるまで再利用出来ないなんてもったいないなと思うことが頻繁にあるかと思います。使わないと分かっているオブジェクトを再利用出来たら、そんな夢をかなえるのが名前にやたらmが多いmruby-mmm(https://github.com/miura1729/mruby-mmm)です。mmmはManual Memory Management の略です。

こんな感じで使います。

class Foo
  include MMM

  def initialize
    @a = 1
  end
end

a = Foo.new
fst = a.inspect
a.move
a = Foo.new
amov = a.inspect
a = Foo.new
anew = a.inspect

再利用したいオブジェクトのクラスは、

include MMM 

というおまじないをつけます。

というおまじないをつけます。2行にわたるのは、newをフックするのとmoveというメソッドを追加するためですが煩雑なので1行だけですむ方法があればいいなと思います。

オブジェクトの生成は普通にnewで出来ます。この先もう使わないよという場合はmoveメソッドを呼び出します。そうすると、こっそりクラス変数moveしたオブジェクトキャッシュされ、つぎにnewしたときにヒープではなくキャッシュされたオブジェクトが再利用されます。moveってどっから来たの?と思われると思いますが、C++のstd::move(http://d.hatena.ne.jp/gintenlabo/20110116/1295195945)からきています。

moveの処理は、こんな感じです。

static mrb_value
mrb_mmm_move(mrb_state *mrb, mrb_value self)
{
  struct RObject *obj = mrb_obj_ptr(self);
  struct RObject *cls = (struct RObject *)obj->c;

  mrb_obj_iv_set(mrb, cls, mrb_intern_lit(mrb, "__objcache__"), self);

  return self;
}

このmruby-mmmはmrubyのJITも標準でサポートしています。オブジェクトをいっぱい生成するao-benchをmruby-mmm対応してどれくらい速くなるか調べてみました。

ao-benchに適当にmoveで再利用してもいいよということを指定します。改造したao-benchはここにあります。 https://github.com/miura1729/mruby-mmm/blob/master/sample/ao-render.rb

実行時間はこんな感じになります。i5 2.7GHz Windows7+Cygwin でmrubyのJITです。

ノーマル

$ time bin/mruby.exe benchmark/ao-render.rb
P6
64 64
255

real    0m3.050s
user    0m2.979s
sys     0m0.030s

mruby-mmm対応

$ time bin/mruby.exe mrbgems/mruby-mmm/sample/ao-render.rb
P6
64 64
255

real    0m2.490s
user    0m2.464s
sys     0m0.015s

こんな感じで効果はあります。使うのは面倒だけど。将来は、エスケープ解析やデータフロー解析してmoveを自動挿入出来るといいなと思います。