Gauche:プロファイラAPIの使用例
Shiro(2010/03/25 02:58:10 PDT): 既にあるプログラムに対して、後つけで 「この手続きの間だけプロファイラをon/offできないか」という話。 http://practical-scheme.net/chaton/gauche/a/2010/03/25#entry-4bab1039-2c8af
特定の式の実行中だけプロファイリング
プロファイラはScheme側からon/offできるので、コードが書き換えられるなら こんなことができる。
(define-macro (profiling expr)
`(dynamic-wind
profiler-start
(lambda () ,expr)
profiler-stop))
(profiling 式) とすれば式の実行中だけプロファイルがとれる。
例えば、適当に重い計算をでっちあげといて:
(define (fib n) (if (<= n 1) 1 (+ (fib (- n 1)) (fib (- n 2)))))
(define (heavy-calc) (fib 35))
(define (tarai x y z)
(if (<= x y)
y
(tarai (tarai (- x 1) y z)
(tarai (- y 1) z x)
(tarai (- z 1) x y))))
(define (medium-calc) (tarai 18 9 6))
これが対象となる計算だとする。
(define (some-calc) (dotimes (n 3) (+ (heavy-calc) (medium-calc))))
medium-calcのとこだけプロファイリングしたいな、と思ったら こんなふうに書き換えて:
(define (some-calc) (dotimes (n 3) (+ (heavy-calc) (profiling (medium-calc)))))
実行。プロファイラはデータを蓄積してゆくので、profiler-resetでデータをクリアしてから 対象コードを走らせて、profiler-showで結果を見る。
gosh> (profiler-reset)
#<undef>
gosh> (some-calc)
#t
gosh> (profiler-show)
Profiler statistics (total 155 samples, 1.55 seconds)
num time/ total
Name calls call(ms) samples
---------------------------------------------------+------+-------+-----------
tarai 21275559 0.0001 155(100%)
medium-calc 3 0.0000 0( 0%)
profiler-stop 3 0.0000 0( 0%)
「そうかー、medium-calcが重いのはtaraiのせいだったのかー(棒読み)」
参考: http://practical-scheme.net/gauche/man/?l=jp&p=profiler-start
実行中のコードに動的にアタッチできないか
サーバプログラムなど、既に実行中のプログラムに対して後つけで 「この手続きだけプロファイル取りたい」というのを 仕込みたい場合。少々強引だが、こういう手がないわけではない。
(define (take-profile proc)
(lambda args
(dynamic-wind
profiler-start
(lambda () (apply proc args))
profiler-stop)))
(define *hooked-procedures* '())
(define-macro (attach-profiler! name)
`(unless (assq ,name *hooked-procedures*)
(push! *hooked-procedures* (cons ',name ,name))
(set! ,name (take-profile ,name))))
(define-macro (detach-profiler! name)
(let1 p (gensym)
`(and-let* ((,p (assq ',name *hooked-procedures*)))
(set! ,name (cdr ,p))
(set! *hooked-procedures* (delete! ,p *hooked-procedures*)))))
先ほどの例のコードで。
まずは定義を戻しておいて:
gosh> (define (some-calc)
(dotimes (n 3) (+ (heavy-calc) (medium-calc))))
some-calc
普通に実行してもプロファイルは取れない。
gosh> (profiler-reset) #<undef> gosh> (some-calc) #t gosh> (profiler-show) No profiling data has been gathered. #<undef>
今度はheavy-calcにアタッチしてみよう。
gosh> (attach-profiler! heavy-calc)
#<closure (take-profile take-profile)>
gosh> (some-calc)
#t
gosh> (profiler-show)
Profiler statistics (total 669 samples, 6.69 seconds)
num time/ total
Name calls call(ms) samples
---------------------------------------------------+------+-------+-----------
fib 89582109 0.0001 669(100%)
profiler-stop 3 0.0000 0( 0%)
heavy-calc 3 0.0000 0( 0%)
#<undef>
gosh> (detach-profiler! heavy-calc)
()
some-calcがサーバループで実行されてる場合でも、 サーバにREPLの口を開けておけば、 attach-profiler!した後のheavy-calcの呼び出しからはプロファイルが取れる。
注意: 上のattach-profiler, detach-profilerは手続名への束縛をset!で 置き換えているのだけれど、R6RSではモジュールからexportされている 束縛にはset!できないことになっている。この仕様は最適化と相性が良くて、 Gaucheでも実はそうしようかという考えは何年も前からあって、 そういうモードも既にコードには仕込んである。 もし将来、そのモードをデフォルトにするなら、上のattach-profiler/detach-profiler は動かなくなる。おそらく、一般的なset!を使うのではなく、こういう特殊な用途 向けにのみ置き換えを許すようなAPIを別に設けることになると思う。 ここのコードを使う際は留意されたし。