toy VM 続き
gauche toy VM はまだ遠いので、SECD machine の続き。
LispMe の VM は SECD Machine をちょっと拡張したものになっている。特に理解しておきたいのが、シンプルに書けるという continuation(6.7,7.5) と、tail call(6.6, 7.4)。
まず末尾呼び出しの最適化。TAPC という命令と、コンパイラが必要。そのまえに、LispMe ではクロージャの表現を拡張している(6.5)。これはエラーチェックのためのようなので、当面は無視する。
テスト中。
gosh> (time (secd 's 'e '(DUM NIL LDF (LDC 0 LD (1 . 1) = SELR (LD (1 . 2) RTN) (NIL LD (1 . 2) LD (1 . 1) * CONS LDC 1 LD (1 . 1) - CONS LD (2 . 1) TAPC)) CONS LDF (NIL LDC 1 CONS LDC 20 CONS LD (1 . 1) TAPC) RAP) 'd)) ;(time (secd 's 'e '(DUM NIL LDF (LDC 0 LD (1 . 1) = SELR (LD (1 . 2) RT ... ; real 0.009 ; user 0.010 ; sys 0.000 ((2432902008176640000 . s) e () d) stop
プロファイルを取ろう
gauche はまだ実験的とあるが組み込みプロファイラを持っている。プロファイラAPIを使うと対話環境でプロファイルを取得できる。
とりあえずこんなマクロを定義して、
(define-macro (with-profiler . body) (let1 result (gensym) `(begin (profiler-reset) (profiler-start) (let1 ,result ,@body (profiler-show) (print ,result)))))
プロファイルを取ってみた。まずは gauche の普通の fib 。
gosh> (with-profiler (letrec ((fib (lambda (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))))) (fib 20))) Profiler statistics (total 1 samples, 0.01 seconds) num time/ total Name calls call(ms) samples ---------------------------------------------------+------+-------+----------- fib 21891 0.0005 1(100%) undefined? 3 0.0000 0( 0%) profiler-show 1 0.0000 0( 0%) profiler-raw-result 1 0.0000 0( 0%) profiler-get-result 1 0.0000 0( 0%) 6765 #<undef>
そして secd machine 版。
gosh> (with-profiler (run '(letrec ((fib (lambda (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))))) (fib 20)))) Profiler statistics (total 358 samples, 3.58 seconds) num time/ total Name calls call(ms) samples ---------------------------------------------------+------+-------+----------- secd 306480 0.0095 291( 81%) equal? 2046961 0.0003 54( 15%) (secd G719) 21891 0.0032 7( 2%) list-ref 153236 0.0003 4( 1%) locate 76618 0.0003 2( 1%) number? 27 0.0000 0( 0%) compile 21 0.0000 0( 0%) 省略
気付いたこと。
- あくまで secd 関数が呼ばれた回数しか分からない。LDF (load function) の呼ばれた回数を数えてもよい。(その先はプロファイラ付き vm だろうか。)
- equal? は match のマクロ展開後に出てくるもの。util.match はシンボルとの比較でも、必ず (if (equal? (caaddr G636) 'CAR) ...) というような形で equal? を使うようだ。eq? で比較するようにすれば少し効率的になるかもしれない。
- TAPC を用いると少し secd の呼び出し回数が減る。
- 遅い。中間層一つで10倍遅くなる(と誰かが書いていたと思うのだが出典がみつからず。)としても、遅すぎる気がする。せめて100倍遅いくらいにしたい。
- 今思いついたが最適化オプションをオフにしたら差が縮むかもしれない。