psyntax.pp の Hello, World を高速化しようその1
ここ数日 Hello, World の高速化のためにプロファイラの精度を上げるべく作業をしていた。
無名の closure でもソースコード上の位置が記録されたりとかまあいろいろがんばりました。
time% msec calls name location 27 3600 190 (<proc> x y) compiler-with-library.scm:8437 8 1150 143356 (<proc> i) 8 1070 4039 (pass3/$lambda iform loca...) compiler-with-library.scm:9192 5 700 - (<top-level>) 4 550 8456 (pass3/$call iform locals...) compiler-with-library.scm:9043 3 500 197791 (<proc> s) compiler-with-library.scm:9661
以下作業ログ。
- おおざっぱにコメントアウトを2分法で行ったところ遅いところが判明。
- psyntax.pp の (begin (define g$120$23359 '#f) から始まるブロックが遅い。
- コンパイル自体が遅いのが理由ではないことが分かったのでログで (sys-gettimeofday) を挟んで2分法でさらに絞り込もうと思う。
- いらない部分をそぎ落とした psyntax-mosh.pp.1 32秒
- コードをみやすくするために set! を置換して改行を入れる 32秒
- コードが 600 行あるので 3 箇所にログを入れてみる。
- 何も実行していない段階で一つ前のブロックから32秒も経っている。これはコンパイルが遅いことを意味するのだろうか。
- たぶんコンパイルが遅いのだが念のためログをとってみる。
- compile =32196 eval = 0 ビンゴです。
- コンパイルするようなコードを書こう。 psyntax.compile
- これの実行には 18秒。
- lambda の body を空にしてみる。これが理論限界。
- 0 sec
- -p でプロファイラを実行
time% msec calls name 8 1770 1636539 set-cons 1 310 1644841 set-union 1 230 618504 $map1 0 10 4151 pass3/find-sets
- 確かにコンパイラ内で遅い。ただ総実行時間が18秒なのにどこに行った?
- まずはプロファイラを疑おう。簡単なスクリプトを書いて確かめる。
- set-cons を 1636539 回呼ぶ。
- (set-cons 1 '()) 0.8sec
- (set-cons 'a '(1 2)) 0.75sec
- リスト長4 => 0.74
- リスト長8 => 0.78
- リスト長16 => 0.94
- リスト長32 => 1.482
- リスト長64 => 1.732
- リスト長128 => 3.009
- リスト長256 => 5.251
- これだな。考えなければいけないこと
- set-cons はどこから呼び出されているか。
- set-union からのみ呼ばれている。これは internal define にすべきだな。
- set-union はどこからよばれる?
- pass3/$call
- pass3/$lambda
- pass3/$receive
- pass3/$let
- pass3/$letrec
- pass3/$lambda で今回は呼ばれまくっている。
- 引数のメモ化は使えないか
- どういう文脈で使われているか。少しでも回数を減らせないか。
- find/free で can-frees が使われている。
- set-cons/ set-union は1回の呼び出しで指数的に呼び出しが増える。
- 本当に set-cons が遅いのか怪しくなってきた。
- vmcpp のコンパイラだけ変更したい。
- cond-expand を実装しよう。
- pass3/$lambda の set-cons/set-union を '() で置き換える。
- 実行時間にほぼ変化なし。つまり間違っていたと。
- -p でも詳しい情報得られず。
- さてここで問題はなんだろうか?
- コンパイラが遅いことが分かっているのにプロファイラがそれを検知できていないことだ。
- 匿名クロージャもプロファイル対象にしよう。
- シグナルハンドラが stop しても動いているような気がする。
- プロファイラで合計を出すところで hashtable->alist が C のスタックオーバーフローを起こしている。
- これは map 内で使われている apply がC的に再帰しているからだ。これを改める方法は分かっている。
- いろいろたまってきたので整理しよう
- 帰ったらやること。
- Gauche でソース情報がうめ込めるか?
- まずは library.scm でエラーを起こしてソースコード情報が表示されるかをチェック。
- 表示されるようであれば OK 。だめなら探る。
- だめだった。つまり。
- □compiler-with-library で prittey printer を使おう
- フルパスやめようぜ。
- ■←applyの再帰版を作る。 rev 119
- プロファイラに含まれていない手続きがあるね。
- あ。。。 callHash_ に含まれていないのって getCompilerしてロードしたものでは?
- srfiの件問題点をまとめよう。
- 起動時に http-get などを登録できるように g000001 さんからの提案
- Gauche でソース情報がうめ込めるか?
- returnCode をインスタンス変数にする
- pc_ の順序を変える
- うまくいかなかったら rev 117 に戻す。
- getProfileResult の冗長な部分を直す。
- □ display closure に時間がかかっているので display closure の命令にソースコード情報を埋め込みたい
- □ display 命令は主に let で使われるので let の src コード情報がどこまできているのかを調べる。
- □ display closure をプロファイラの結果に含めてしまうから問題がややこしくなる。
- レジスタを1つ増やして display closure ではなく本当に call された closure だけをあつめる方法を追加しよう。
- うまくいった。