LuneScript のトランスコンパイル高速化 (スタック割り当て)
今回の記事は、 先日検討した LuneScript のクラスのオブジェクトを スタックに割り当てて高速化できるかどうか?の検討結果です。
結果
今回の検討結果は以下の通りです。
「スタック割り当て自体は有効ですが、 スタック割り当てから escape されないように設計しないと効果を得られません。」
なんだか当たり前な検討結果ですが、そうなんだから仕方がない。
では、なぜそのような結果になったかを説明していきます。
検討内容
LuneScript でオブジェクトをスタック割り当てするには、 そのオブジェクトのクラスは次の条件を全て満す必要があります。
- 全てのメンバが immutable
- Super クラスがない
- Sub クラスがない
この条件にマッチし、なおかつ生成数の多いクラスは以下になりました。
-
Positon
- トークンの位置情報
-
Token
- Parse したトークン情報
go の pprof 機能の解析によると、 この 2 つの合計は、生成している全オブジェクト数の 5% ほどになります。
このクラスをスタック割り当てに変更してみたところ以下の結果になりました。
-
Positon をスタック割り当てに変更
- トランスコンパイル時間が 1% 程度 改善
-
Token をスタック割り当てに変更
- トランスコンパイル時間が 10% 程度 悪化
この通り、クラスによって結果が異なりました。
先日の記事で書いたように、オブジェクトをスタック割り当てする場合、 そのオブジェクトを最後までスタック割り当てで扱わないと逆に効率が悪くなります。
では、何故スタック割り当てで扱わないケースがあるのかと言うと、 LuneScript には nilable があるからです。
nilable を表現するために、 go の interface{} を利用しています。 そして、スタック割り当てのオブジェクトを interface{} に変換すると、 escape されます。
このようなケースを改善するには、異常値の表現に nilable は使わずに、 特別な値を定義する必要があります。 あるいは Rust の Option 型のような型を用意する必要があります。
また、 List や Map などの collection 型は interface{} として値を保持します。 そして stem 型も interface{} として保持します。 つまり、collection 型, stem 型で管理することが前提の場合、 スタック割り当て化は逆効果です。
ただ、collection 型を使えないのは流石にハードルが高いので、 スタック割り当てのまま collection 型を使えるように改善したいと考えています。
ということで結論は以下になります。
スタック割り当て自体は有効ですが、 スタック割り当てから escape されないように設計しないと効果を得られません。
今後の予定
今の段階(6827a64)で、 セルフホストのトランスコンパイルの内部処理時間は 0.9 秒弱まで短縮できていますが、 time コマンドの計測結果では 1.1 秒前半です。
この原因の一つに、 collection 型で interface{} を利用していることが挙げられるので改善したいところです。 しかし、これを改善するには go 側の slice, map の generics 対応が必要になります。
よって、go 側の slice, map の generics 対応を持ってから、 collection 型の改善が出来るように対応を検討します。
それまでは、 大きな改善は見込めなさそうなので LuneScript の高速化対応を一旦中断します。