読者です 読者をやめる 読者になる 読者になる

TypeScriptのコンパイルは遅いけど、それでもgrunt-typescriptが最速じゃないだろうか?

この内容は grunt-typescript 0.3.2 (typescript 1.0.0) をもとに記述しています。

ついさっき grunt-typescript 0.3.2 をリリースしたわけですが、ちょっとコンパイルを早くしてみました。ってことで typescript 自体のコンパイルの速さを tsc と比較して見ました。

grunt-typescript は以下のように指定してます。

typescript:{
  base:{
    src: ["src/compiler/tsc.ts"]
  }
}


まずは tsc コマンド。

$ time tsc src/compiler/tsc.ts

real    0m15.641s
user    0m0.000s
sys     0m0.015s

で、じつは私の環境が WindowsVisual Studio 2013 の Update 2 RC が当たってる状態なので、この tsc コマンドは WSH で動いてて、node で動いてないです。なので、node で動くように ntsc ってコマンド作ってそちらでも試してみました。内部的には node コマンドで tsc.js を呼んでるだけです。

$ time ntsc src/compiler/tsc.ts

real    0m15.543s
user    0m0.030s
sys     0m0.030s

0.1秒くらい早そうですが、完全に誤差の範囲ですね。

最後に grunt-typescript 。

$ time grunt typescript:base
Running "typescript:base" (typescript) task
117 files created. js: 117 files, map: 0 files, declaration: 0 files (13875ms)

Done, without errors.

real    0m14.700s
user    0m0.015s
sys     0m0.060s

grunt-typescript のほうが 1 秒くらい早いです。

なぜ早くなっているか

grunt-typescript は基本的には tsc.ts のコードを参考に作ってて、少し Grunt 用の処理をいれて作ってます。なので tsc を直接実行するのにくらべて若干遅くなるくらいでした。で、今回(実際は0.3.0から)ちょっと内部処理に手を出して、速度アップをはかってみました。

tscコンパイルのステップとして各ファイルごとにざっくり書くと Syntax のチェック、 Semantic のチェック、 JS の生成、 d.ts の生成とやっているのですが、この Syntax のチェックと、Semantic のチェックですが、lib.d.ts についても実行しています。ですが、 lib.d.ts って typescript の組み込みみたいなもので、そもそも syntax や semantic にエラーがあったら駄目で、前提としてチェックしなくっても問題ないはずなので、 lib.d.ts を対象から外すようにしました。 lib.d.ts ファイルは結構大きなファイルなので、これを外すことで 1 秒くらい稼げました。

ちなみに、 grunt-typescript 0.3.1 で lib.d.ts に syntax チェックと semantic チェックをを含むようにしたら以下の実行速度でした。

$ time grunt typescript:base
Running "typescript:base" (typescript) task
117 files created. js: 117 files, map: 0 files, declaration: 0 files (15373ms)

Done, without errors.

real    0m16.190s
user    0m0.030s
sys     0m0.031s

今後 typescript の内部構造が変更されたら動かなくなる可能性もあるので少し怖いところもあるのですが、、、。
というか、 lib.d.ts を ES3 のみとか ES5 のみとか HTML DOM用とかの何種類かに分けてくれたらもっと嬉しいのですが、リクエストでも投げてみようかしら。

さらに早い watch オプション を使った2回目以降のコンパイル

さらに watch オプションでファイル監視しているときの2回目以降のコンパイルは、変更されたファイルのみ読み込みと書き出しをするのでさらに早いです。以下のような感じ。

$ grunt typescript:watch
Running "typescript:watch" (typescript) task

Watching directory.... c:\Work\Git\typescript\src\compiler
>> Changed c:/Work/Git/typescript/src/compiler/tsc.ts
117 files created. js: 117 files, map: 0 files, declaration: 0 files (14036ms)

Watching directory.... c:\Work\Git\typescript\src\compiler
>> Changed c:/Work/Git/typescript/src/compiler/tsc.ts
1 file created. js: 1 file, map: 0 files, declaration: 0 files (8761ms)

Watching directory.... c:\Work\Git\typescript\src\compiler

tsc.tsを試しに変更-保存して結果のところを確認すると、1回目は117ファイルが出力されていますが、2回目は1ファイルのみが出力されていますね。ファイルの変更日付を確認しているので複数ファイル更新されたら、そのファイル数分だけ対象にします。実行速度についても大体6秒ほど早くなってますね。これは対象となっていないファイルは読み込みと書き出しを実施していないのは書いた通りですが、それだけではなく、先に書いたコンパイルのステップの JS の生成も外しているからです。

ただし、単一ファイルで出力する tsc でいうところの --out オプションがついている状態だと lib.d.ts が外れている恩恵だけなのでここまでは速くならないのですが。まぁ、それでも tsc コマンドを叩くよりかはコンパイルが早くなるかと。


と、ここまで書いて Syntax のチェックと、 Semantic のチェックもいらないような気がしてきました。"対象になっていない = 先の結果と同じ" なはずなのでイケそうな気がします。ちょっと確認してみよう。。。