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を別に設けることになると思う。 ここのコードを使う際は留意されたし。