Open7

Ribbon: スクリプトの起動が遅いのをなんとかしたい

okuokuokuoku

とりあえず適当なスクリプト(普通のSchemeインタプリタなら500msくらいで終わる)を起動すると:

FIXME: [0] yielded exact zero?
New addition?? [vmfetchcode]
(ARGS: ("-yuniroot" "/home/oku/repos/ribbon-integ/yuni" "-runtimeroot" "/home/oku/repos/ribbon-integ/yuniribbit-proto" "iter0.sps"))
(ARGS: ())
/* ★★ ここで時間掛かる1 ★★ */
(STARTING...: "iter0.sps")
(READBUF: 0)
(READDONE: 512)
(READBUF: 512)
(READDONE: 512)
/* ★ ここはネイティブ化したので一瞬 */
(READDONE: 11)
(READBUF: 0)
(READDONE: 11)
/* ★★ ここで時間掛かる2 ★★ */
(MAKESYM: check-finish #t)
(MAKESYM: check-equal #t)
(LOCAL: %%yunitest-mini-proc-fail! (yunitest mini))
(LOCAL: %%yunitest-mini-failed-forms (yunitest mini))
(RENAME: cons (yunitest mini))
/* ★ 以降もインタープリトなので一瞬 */
real    0m7.118s
user    0m7.031s
sys     0m0.061s

(ちなみにReaderを移行する前は12秒だったので3秒ほど短縮はしている。)

2箇所で時間が掛かっているように見える。どこかにO(N)が有りそうだからprintを足していって、どこに時間掛かってそうか特定しないといかんな。。特に時間が掛かっているのは2なので、そっちを先に見よう。。

okuokuokuoku

1 = ensure-library-loaded!

最初に時間が掛かっているポイントは、フロントエンドの初期化でライブラリをロードしているところだった。

https://github.com/okuoku/ribbon/blob/e912c334dd6d00c30ceff1dd3ca8ac14e88657e8/boot/start.sps#L134

これはフロントエンド側の ensure-library-loaded! に至る。

https://github.com/okuoku/yuniribbit-proto/blob/0c59f7af7fabc4e7396b96bf142713382601f326/ribbon/util/compiler.sls#L44-L46

https://github.com/okuoku/yuni/blob/f3e41162fc8f25571c11bea34bdfe5d28b56c44a/lib/yunife/core.sls#L220-L236

これは普通のSchemeコードだからインタプリタ側を最適化するしかないな。。Schemeの大半をSchemeで実装している都合上、標準ライブラリである (yuni scheme)をimportするだけで大量のライブラリをロードしてしまう。

okuokuokuoku

Cセーフシンボルへの変換を止めてみる

Ribbonでは将来的にCソースコードを出力することを見越して、ライブラリは内部でCシンボルとしても正当なシンボル名で処理するようにしている。

https://github.com/okuoku/yuni/blob/f3e41162fc8f25571c11bea34bdfe5d28b56c44a/lib/yunife/core.sls#L166-L183

これをno-opにしてみた。 (define (csafe str) str)

real    0m7.103s
user    0m7.015s
sys     0m0.061s

real    0m4.078s
user    0m4.000s
sys     0m0.046s

減り方がえぐいな。。これは事前計算できるんだし事前計算した方が良いね。。リリースビルドなら2秒切るしこれで良い気もしてきた。

でも (yuni scheme) のバンドル化をすれば、Cシンボルの導出回数自体も減るので効果という意味では何ともだな。

okuokuokuoku

ライブラリのバンドル、マージ

開発中は複数に分かれているライブラリを1つに纏めてしまうこと自体は理に叶っている。実際npmで提供されるようなJavaScriptのライブラリはライブラリであってもbundlerで纏めてしまうことが一般的になされている。

というわけで、 (yuni scheme) を構成するライブラリは 個別には import できない ルールにしてみるのが良いのではないか。。従来は、 (yuni scheme) はyuniのライブラリの一部である (yuni miniread *) に依存していた。

サブライブラリ化

ただし、全てのライブラリを単純にマージしてしまうと、 VMのプリミティブに直接アクセスしたいようなケースで困ってしまう。 Ribbonの場合は (rvm-primitives) ライブラリで記述されているプリミティブはスクリプト中から直接呼出せるようにしている。

https://github.com/okuoku/yuniribbit-proto/blob/0c59f7af7fabc4e7396b96bf142713382601f326/runtime/rvm-primitives.sls#L1-L12

スクリプトから (import (rvm-primitives) ...) できる状況はキープしなければならない。サブライブラリを宣言できるようにし、サブライブラリのexportは保存する。つまり、ライブラリ (yuni scheme) のバンドルには2つのSchemeライブラリ (yuni scheme)(rvm-primitives) が含まれているように見せる必要がある。( (yuni scheme)(rvm-primitives) に依存しているため、 (yuni scheme) のimport時に纏めてimportできるように合成してしまうのが合理的と言える。)

ライブラリバンドルへの変更

https://github.com/okuoku/yuniribbit-proto/blob/0c59f7af7fabc4e7396b96bf142713382601f326/ribbon/util/compiler.sls#L188-L191

ライブラリバンドルは以下の vector のリストとなっている:

idx sym 内容
0 libname ライブラリ名(リスト)
1 libsym ライブラリシンボル
2 import* インポート対象のライブラリシンボルリスト
3 imports インポート対象のライブラリ(リスト)
4 exports エクスポートしているシンボルのリスト
5 prog ★ リストにする: 実行コードのVM命令列
6 macnames* マクロ名のリスト
7 maccode ★ リストにする: マクロ変換コードのVM命令列
8 aliassym ★ 追加: エイリアス先のライブラリシンボル または #f

aliassymが偽の場合のインポート処理は:

  1. importsに含まれるライブラリを全てロードする
  2. prog および maccodeに含まれるVM命令列を全て実行する (= exportsに設定されているシンボルがVM内に定義される)
  3. 名前空間にexportsやmacnamesを追加する

という処理になる。aliassymがシンボルである場合は、シンボルが指すライブラリを代わりにロード(stage)し、その後名前空間の処理(import)を行う。

stage / importの分離

ライブラリのVM命令列を実行済かどうかを示す stage と、プログラムの名前空間に追加済かどうかを示す import にライブラリのロードを分割する。

okuokuokuoku

超早くなるけどimplicit exportをどうすんのか問題

これかなり高速化する。。もうこれで普段使いできそうだな。

real    0m1.676s
user    0m1.609s
sys     0m0.046s

ただ、単純にライブラリをマージするだけだと、implicitなexport -- マクロ内でinjectされる暗黙の参照を処理できないことがわかった。とりあえず面倒だけど手動でそういうシンボルを持っているライブラリを追加することで対策している。

https://github.com/okuoku/yuniribbit-proto/blob/78269be7a8b8c10eff6ce3d2de02e8de976a30d3/ribbon/util/mergebundle.sls#L6-L10

今回mergeするのは (yuni scheme) だからSchemeの標準ライブラリで使われるものだけ指定すれば良いけど、一般的なライブラリをmergeしないといけない場合は困るな。。

okuokuokuoku

とりあえず実装

https://github.com/okuoku/yuniribbit-proto/commit/78269be7a8b8c10eff6ce3d2de02e8de976a30d3

https://github.com/okuoku/ribbon/commit/7d3d5f370f9fd1f7ccc165d1b7b07327b64a7e1c

地味に unquote が壊れていたのを直した。。

https://github.com/okuoku/yuni/commit/1c35e56c528b9d2152cd40315b448acdad7b838d

とりあえずyuniのテストは普通の処理系と遜色ない速度で実行できるようになったので、そろそろyunibaseに登録しても良いかもしれない。