計測 & トライ - Scheme VM を書く
さて未だに「何をどう変えたら、どのくらい速くなるか?」などの感覚は得ることは出来ていませんが、計測の重要さは分かってきました。
そこでベンチマークスクリプトを書いて簡単に計測結果を日記貼り用出力に変換するところまで面倒を見ることにしました。
複数回ベンチマークを走らせて平均をとるなどもそいつがやります。
前回の計測から特に PUSH, CONSTANT, APPLY, REFER_FREE, REFER_LOCAL0 の命令たちが多く呼ばれていることが分かりました。
これらの命令が少しでも速くなれば全体の高速化につながるのではないかということでトライしてみます。
ここでは大きく構造を変更することはしません。
PUSH
PUSH を高速化するには?
void push(Object obj) { if (sp_ >= stackSize_) { RAISE1("stack over flow sp=~d\n", Object::makeInt(sp_)); } stack_[sp_++] = obj; }
スタックオーバーフローチェックを無くすことができれば多少速くなるかもしれません。
無くせるかはおいておいて、どの程度速度向上するか見てみましょう。
benchmark | sec |
---|---|
./bench/fib.scm | 0.091 |
./bench/case.scm | 0.417 |
./bench/let.scm | 0.080 |
./bench/tak.scm | 0.061 |
./bench/decode.scm | 0.030 |
↓
benchmark | sec |
---|---|
./bench/fib.scm | 0.080 |
./bench/case.scm | 0.400 |
./bench/let.scm | 0.081 |
./bench/tak.scm | 0.061 |
./bench/decode.scm | 0.027 |
効果ありますね。これは測って良かった。これくらいのコード削除でこれだけ変わるのか。
さて方針としては 3 つくらいあるかな。
- ユーザーフレンドリーさを考え今まで通りチェックする
- 可能であればコンパイル時にスタックオーバーフローを起こすかを解析する。(大変そう)
- スタックのサイズを大きめにする。デバッグビルド時だけスタックオーバーフローをチェック。
うーん。保留にしよう。
CONSTANT
簡単な最適化の余地はなさそうでした。
REFER_FREE
(REFER_FREE n) の n が境界を超えていないかのチェックは省略しても良いと思うので省略。
コンパイラのバグでもないと境界は超えないのでデバッグビルドのときだけチェックするようにした。
速度は全く向上せず。PUSH ほどの回数では実行されていないからかな?
REFER_LOCAL, REFER_LOCAL0
スタック参照するだけなので余地がなかった。
APPLY
APPLY はコードが長いので最適化の余地がありそうです。
RETURN 命令列の生成を毎回するのではなくて初期化時に1回だけやるようにしてみましょう。
./scripts/bench.scm > /dev/null
benchmark | sec |
---|---|
./bench/fib.scm | 0.092 |
./bench/case.scm | 0.434 |
./bench/let.scm | 0.089 |
./bench/tak.scm | 0.061 |
./bench/decode.scm | 0.030 |
↓
benchmark | sec |
---|---|
./bench/fib.scm | 0.090 |
./bench/case.scm | 0.421 |
./bench/let.scm | 0.081 |
./bench/tak.scm | 0.061 |
./bench/decode.scm | 0.027 |
全体的に速くなりました。
あとコードを読んでいて const つけ忘れが目立つのできっちりつけてみました。
benchmark | sec |
---|---|
./bench/fib.scm | 0.090 |
./bench/case.scm | 0.405 |
./bench/let.scm | 0.083 |
./bench/tak.scm | 0.061 |
./bench/decode.scm | 0.030 |
あまり変わりません。でもバグを見つけることができたので良しとします。