LuneScript のトランスコンパイル時間を 425 パーセント改善した件

Page content

LuneScript は Lua 向けのトランスコンパイラで、 LuneScript 自体も Lua 上で動作しています。

また、LuneScript は LuneScript 自体の処理を、 LuneScript で開発する所謂セルフホスティングを採用しています。

そのセルフホスティングしているコード規模は、右肩上がりで増大しています。

../lunescript-codesize.svg

上記グラフは少し以前のもので、現在は 50Kline を突破しています。

コード規模が増えて一番気になるのは、やはりコンパイル時間です。

特に LuneScript は Lua で動作するため、 一般的なネイティブのコンパイラよりも遅くなります。

一年以上前から速度向上のための取り組みは行なっていましたが、 今回ようやく速度向上版を安定して運用できるレベルまで到達しました。

そして速度向上の結果、従来と比較して 425% 改善しました。

(2020/11/8) 更新

そして速度向上の結果、従来と比較して 387% 改善しました。

以下は、セルフホストしている LuneScript コードをトランスコンパイルする際に掛る時間を、 改善前と後とで測定した結果です。

改善前(lua) 改善後(go) 参考 (luajit)
20.67 sec 4.86 sec 21.56 sec

この表の通り、 (/ 20.67 4.86) 4.253086419753086 ≒ 425% 改善しています。

以降では、今回の LuneScript 性能向上の実現方法について説明します。

セルフホスティング

前述の通り LuneScript は次の特徴があります。

  • LuneScript 自体 Lua で動作する
  • 一般的に Lua はネイティブと比べて遅い

この特徴から、 Lua ではなく、ネイティブで動く LuneScript コンパイラを作成するのが、 性能向上のための最も確実性の高い手段だと考えられます。

ネイティブで動くプログラムを組むには、 当然ネイティブに対応したコンパイラが必要になります。

当然ながら、 LuneScript のコードに対応したコンパイラは LuneScript 以外にありません。

また、 Lua のコードに対応したコンパイラもありません。 Lua には、JIT コンパイラに対応した LuaJIT がありますが、 上記の表の通り LuaJIT では LuneScript の速度向上は実現できませんでした。

ではどうすれば LuneScript をネイティブで動かせるか?

次の方法が考えられます。

  1. ネイティブのコンパイルに対応した別の言語で LuneScript を開発する
  2. セルフホストしている LuneScript コードを、ネイティブコードにコンパイルできるように LuneScript を拡張する

上記の 1) は、 LuneScript の特徴であるセルフホスティングを止めるということです。 しかし、セルフホスティングは LuneScript にとって非常に重要な特徴です。 セルフホスティングが重要な理由はいくつかありますが、 品質を担保するという意味での重要性については、以下を参照してください。

<https://ifritjp.github.io/documents/lunescript/test/>

よって、 1) は却下し 2) で対応しています。

ネイティブコードにコンパイルする方法

「ネイティブコードにコンパイル」するには、次の方法があります。

  1. LuneScript から、直接ネイティブコードへのコンパイル機能を LuneScript に拡張する
  2. LuneScript から、別のコンパイラの言語に変換する機能を LuneScript に追加し、 別の言語に変換したソースをそのコンパイラでビルドする

上記 (a) は、独自にコンパイラを作ることになるので、 非常に柔軟に開発することが出来るメリットがあります。 その一方で、多くのことを自分でやらなければならないというデメリットがあります。

上記 (b) は、変換する言語仕様に制限されるというデメリットがありますが、 多くのことを変換先のコンパイラに任せられるというメリットがあります。

(b) はトランスコンパイラそのものであり、 LuneScript との相性が良いと判断し、 (b) を採用しました。

なお、変換先は go を選択しています。

これは、ちょうど go を勉強したいと思っていたタイミングとマッチしていたのと、 静的型付け言語の割には比較的緩く書けるので、 変換先の言語にちょうど良いと考えたためです。

「比較的緩く書ける」のが何故良いのかと言えば、 例えば Rust のように非常に厳格な言語だと、 その言語仕様に併せこむのが困難で、 LuneScript からの変換ができなくなる可能性が高いためです。

LuneScript と Go の言語仕様の差異

LuneScript は、イマドキの言語の多くの仕様を取り込んでいるため、 何気に言語仕様が大きくなっています。

それら言語仕様を、変換先の言語で実現できるかどうかが課題です。 変換先の言語の制約によって、 LuneScript の言語仕様が実現できないことも考えられます。

今回の go への変換については、実現不可能な言語仕様はありませんでした。

ただし、現時点では LuneScript の言語仕様の全てを、 Go 版の LuneScript で実現できているか? というと、実はそうではなく、 LuneScript をセルフホスティングするために必要な言語仕様に限定しています。

セルフホスティングに必要ない言語仕様については、今後対応していきます。

なお、以下の LuneScript の言語仕様については、 Go 言語の文法には直接ないものなので、 変換処理時にいろいろと制御を入れて実現している仕様の一部です。

  • クラス継承
  • 多値返却 (go にも多値返却があるが、 LuneScript とは大きく仕様が異なる)
  • generics
  • ファイル内スコープ
  • nil 安全
  • and or 演算子
  • Lua 言語との連携

別の言い方をすれば、 go 言語では直接的にはサポートされていないこれらの機能も、 コードの書き方次第で go 言語上で実現できるということ です。

LuneScript の言語仕様への影響

今回の go 言語へのトランスコンパイル対応で、 LuneScript の言語仕様を一部修正しています。

できるだけ従来の仕様に影響がないように対応しましたが、 どうしても吸収できない部分があったため修正しています。

具体的な差分ついては、 LuneScript のサイトの方で後日解説します。

<https://ifritjp.github.io/documents/lunescript/>

go 版 LuneScript の利用方法

go 版 LuneScript の利用方法についても、後日 LuneScript のサイトで解説します。

以上。