ahead-of-timeコンパイルとは? 実行前にコンパイルする
ahead-of-time (AOT) コンパイルは、プログラムの実行前にソースやbytecodeをネイティブのマシンコードへ翻訳するため、起動した瞬間にフルスピードに達します。
AOTコンパイルは古典的なモデルです。翻訳作業をすべてbuild中に行い、すぐ実行できるバイナリを出荷し、コンパイルのために実行時には何も払いません。C、Rust、Goのような言語はデフォルトでAOTであり、通常はJITするruntime (.NETなど) も、より速い起動と小さいフットプリントのためにAOTモードを提供することが増えています。
AOTの仕組み
build中に、コンパイラはすべてをターゲットアーキテクチャのネイティブ命令へ翻訳し、runtimeデータなしで最適化します。出力はCPUが直接実行する自己完結した実行可能ファイルで、プログラムの実行中にコンパイルは一切起きません。
AOT vs JIT
JITは実行時にコンパイルし、ライブのプロファイリングを使って最適化できますが、ウォームアップのコストを払います。AOTは即座のピーク速度と予測可能な起動のために前もってコンパイルしますが、runtimeの振る舞いに適応できません。多くのモダンなruntimeは、どちらかを選ばせてくれます。
AOTが輝くとき
- JITをウォームアップするほど長く生きない、短命なプロセス。
- cold startのレイテンシが重要な、serverlessやCLIツール。
- JITコンパイラのオーバーヘッドが大きすぎる、制約された環境。
- 最初の命令から予測可能で再現性のある性能。
トレードオフ
AOTのバイナリは起動が速く予測可能に振る舞いますが、ターゲットアーキテクチャに縛られ、JITが実際のワークロードから行える適応的な最適化を逃します。また、コンパイルのコストを実行時ではなくbuildで払います。
簡単な例
go build を実行すると、ネイティブなバイナリが前もって生成されます。それを実行するとき、コンパイル段階もウォームアップもありません。プログラムは即座にフルスピードで動きます。
CIにおけるAOT
AOTはコストをbuildへ移すため、コンパイルがpipelineの時間を支配し、CPUとメモリに負荷をかけることがあります。複数のアーキテクチャ向けのクロスコンパイルはこれを倍増させます。より大きなrunner (Latchkey) はAOTのbuildを高速化し、コンパイル中の一時的なtoolchainやOOMの失敗は自動でリトライできます。
重要なポイント
- AOTはruntimeの前にネイティブコードへコンパイルするため、プログラムは即座にフルスピードで動きます。
- 短命なプロセスやcold startに敏感なワークロードでは、JITに勝ります。
- コンパイルのコストがbuildへ移るため、AOTのbuildはCPUとメモリを多く使います。