本質的に遅い

試しに、小手先の最適化を全部実装してみた。
つまり、

  • 環境の表現としてhashtableを使う
  • 全てをletから出して一発で参照できるように
  • vectorの代わりにsimple-structを使う
  • バックエンドのletを使う

それでも、make ntestRに掛かる時間は26秒→24秒と微妙な改善に留まった。。少くともメンテナンス性を犠牲にするほどでは無い。
複数のプログラムを起動するようなケースだとあまり平等に比較できないので、r6rs test suiteのrun.spsで比較すると、

  • psyntax-mosh : 未キャッシュ時 5秒、キャッシュ時1秒
  • nmosh : disable-acc時 9秒、キャッシュ時1秒

(psyntax-moshとnmoshでは--disable-accのセマンティクスが微妙に異なる。psyntax-moshの--disable-accはキャッシュの"生成"を行わなくなるので、本当にキャッシュを使わないときはキャッシュディレクトリを削除する必要がある。nmoshの--disable-accは単純にキャッシュを無視する。)
という感じのパフォーマンスになる。nmoshはexpandが異常に遅い。いわゆるLLの良いところはちょっと書いたスクリプトを直ぐ実行できるところにあるので、この特徴は不味い。
キャッシュを使ったときはnmoshの方が微妙に高速になる。nmoshは、展開した後のプログラムも同時にキャッシュするため、キャッシュが有効な時、nmoshは一切の展開作業を行わず単に依存ライブラリが最新かどうかだけチェックする。(原理的にはcompilerとexpanderのロードさえ省略できる)
抜本的な改善のためにはメモリ効率を改善する必要がある。nmoshはマクロ展開のトレースやデバッグ情報の形で多くのトレースを残しているので、それによってメモリの使用量がどうしても多くなってしまう。やはりfluid-letの最適化が必要だろう。。現状のfluid-letはproperな末尾再帰でないので、ループが一切最適化されない。
というわけで、ポストプロセスによる部分的なネイティブ化を考えることにする。psyntax-moshのように手動でネイティブ化するのも可能だが、nmosh(の元となったexpander)は非常にScheme的に書かれているので手でインターフェースを書くのが困難と考えられる。また、一度これをやっておけば、ユーザプログラムに対しても同様の手法で部分的なネイティブ化が適用できるというメリットもある(そもそもnmoshのキャッシュはこれを想定して設計している)。
ポストプロセスを実装するためには、ライブラリの解決まででexpandを止めるパスを準備する必要がある。nmoshの文脈情報は実行時にクエリできるため、プログラム中でmapが使用されていたとして、"R6RSのmapかどうか"を明示的に確定することができる。もっとも、R6RSの特定シンボルに限って実装するならば、nmoshのexpand結果を単に利用することもできる。。(R6RSのmapはexpandしてもmapのままだが、ユーザが定義したmapという手続きはリネームされて別の名前になることが保証されている)