Zig のビルド速度が速くなってきている
(mitchellh.com)- Zig 0.15.1 のリリースにより、コンパイル速度が前バージョン比で大きく改善
- Ghostty プロジェクトで実際のビルド時間を測定した結果、全体として所要時間が短縮
- まだLLVM を一部使用しているものの、独自バックエンドの適用によりさらなる高速化への期待が高い
- インクリメンタルコンパイル (incremental compilation) はまだ完全には実装されていないが、部分ビルドでも性能上の利点が現れている
- 今後さらに高速なビルド環境と向上した開発体験が実現される可能性が高い
概要
Andrew Kelley の「コンパイラが遅すぎてバグが生まれる」という発言を出発点として、Zig は長年にわたりより速いコンパイル時間を目標に、さまざまな構造的改善に取り組んできた
- Zig チームは LLVM の排除、独自のコード生成バックエンドの開発、独自リンカの構築、そして最終的にはインクリメンタルコンパイルの実現に向けて取り組んできた
- こうした長期的な開発の成果が Zig 0.15.1 で目に見える形で現れ始めており、実際のプロジェクト (Ghostty) でビルド時間の変化を測定して共有している
Build Script のコンパイル速度
- Zig 0.14: 7秒 167ms
- Zig 0.15: 1秒 702ms
build.zig スクリプト自体のビルド時間であり、この時間は新規ソースのビルド環境で毎回負担する初期コストとなる
- ビルドスクリプトの再コンパイル頻度は低く保たれているが、ユーザーが初めてプロジェクトを自分でビルドする際の体験に直接影響する
全体の未キャッシュバイナリビルド (Ghostty)
- Zig 0.14: 41秒
- Zig 0.15: 32秒
ビルドスクリプトのビルド時間まで含めた、バイナリ全体のビルド時間
- Zig 0.15 ではさらに約2秒の速度改善効果があり、実際の壁時計時間ベースでも明確な初期差がある
- まだ独自 x86_64 バックエンドは完全には活用されておらず、大半は引き続き LLVM を使用している状況
- 今後 Ghostty が完全に独自バックエンドでビルドされるようになれば、25秒未満まで短縮されると予測されている (従来比で半分程度)
インクリメンタルビルド (Ghostty 実行ファイル)
- Zig 0.14: 19秒
- Zig 0.15: 16秒
意味のある1行の変更 (ターミナルエミュレーションコードへのログ呼び出し追加) の後に再ビルドするのにかかる時間
- ビルドスクリプトと依存グラフはすでにキャッシュ済みの状態での部分ビルド
- インクリメンタルコンパイル機能はまだ完全に実装されているわけではないが、すでに性能改善がはっきり現れている
- 使用中の LLVM を除けば、約12秒程度まで短縮できる可能性がある
- 将来的に真のインクリメンタルビルドが実装されれば、ミリ秒単位のビルド実現も期待される
インクリメンタルビルド (libghostty-vt)
- Zig 0.14: 2秒 884ms
- Zig 0.15: 975ms
1行の変更後に libghostty-vt だけを部分的に再ビルドした時間の測定
libghostty-vtは独自 x86_64 バックエンドで完全にビルド可能なため、LLVM の影響なしに Zig の改善点が直接反映される- インクリメンタルコンパイルでなくても 1 秒未満のビルド時間を達成したのは大きな前進
- 開発者のワークフローにおいて即時フィードバック体験をもたらし、効率を向上させる
- x86_64 と aarch64 のバックエンドは徐々に安定化しており、数か月以内に Ghostty 全体へ適用される可能性がある
ビルド速度改善の現状
- Ghostty の Zig 0.15.1 ビルドは、すべての計測区間で明確に高速化している
- 独自バックエンドとインクリメンタルコンパイルがまだ未完成であるにもかかわらず、現在の成果だけでも十分に印象的
- 今後 1〜2 年でさらに革新的な速度改善が期待される状況
- ビルド速度の観点から、Zig を選んだことは合理的だと実感できる
1件のコメント
Hacker Newsのコメント
1995年に高校を卒業したころ、Intel 486で"Borland Pascal version Turbo Vision for DOS"を再コンパイルしていた時に匹敵するほど、今は本当に速いコンパイル速度を体験している。
Turbo VisionはTUIウィンドウフレームワークで、Borland PascalとC++ IDEの開発に使われていた。
JetBrains IDEを1000MBではなく10MBで実装したキャラクターモード版と言える。
Turbo VisionのWikipedia
LLVMはある種の罠だ。
ブートストラップは本当に速く、あらゆる最適化パスや多様なプラットフォーム対応をただ同然で得られるが、最後の最適化段階やリンク段階の性能を細かく調整する能力を失ってしまう。
CraneliftはRustで近いうちに有効化されると思う。
とはいえ、Rustが初期にLLVMを選んだからこそ今の地位を得られた。
Goはコード生成やリンクを外部に任せず自前で管理することを昔から決めており、その選択の恩恵を大いに受けている。
LLVMが罠だという主張には同意しにくく、Rustはむしろ良い事例だ。
実際、LLVMでコード生成する部分はコンパイラの中でもごく小さな割合しか占めず、置き換えるならcodegen_craneliftやcodegen_gccなどに差し替えることもできる。
SIMDベンダー固有のintrinsics依存は「ロックイン」問題ではあるが、これは言語構造の問題だ。
ほとんどの言語はLLVMバックエンドから始めるのが合理的だ。
C/C++に近い言語ならLLVMのデフォルトパイプラインだけでも最適化がよく効くが、特性の異なる言語ほど独自の最適化パイプラインを書くことになる。
Goのように初期から自前バックエンドを統合してきた例は成功しているように見えるが、特別な差別化要因というわけではなく、自前実装にはかなり大きな機会費用が伴う。
GoとOcamlのコンパイラは本当に速い。
最初から自前ライブラリをしっかり構築してきたし、今では速度面で不利を被ることもない。
今後、1分以上かかるコンパイル環境では働きたくない。
プロジェクトごとに「dev」専用コンパイラを用意し、最終ビルドにだけllvmのような重いものを使ってほしい。
LLVMベースの言語がC++置き換えを目指すなら、依然としてC++に依存することになる。
言語は自分自身だけでブートストラップできるべきだ。
初期には便利なツール群があるが、単なる時間短縮手段であって必需品ではない。
Goチームの下した選択には全面的に共感する。
Craneliftの継続的な成長を期待したい。
今のLLVMはCPUベンダーごとにフォークが乱立し、それぞれの非公開パッケージにCPU別の改善が抱え込まれている。
別の言語フロントエンドを使ったりコンパイラバグが出たりすると、非常に険しい状況になる。
言語が自由に別のバックエンドへ移れるのに、どうして罠だと言えるのかという疑問がある。
コンパイル速度が開発の妨げになるなら、なぜインタプリタを作らないのかという疑問。
実行速度とコンパイル速度は必然的に独立した関係にある。
インタプリタを使えば、コード計測やランタイム制御など追加の開発ツールも簡単に作れる。
最適化されたRELEASEバイナリしかデバッグできないごく少数のケースもあるが、たいていの場合はインタプリタやDEBUGビルドで十分だ。
Rustは安全性、性能、使いやすさの順で、Zigは性能、使いやすさ、安全性の順だと聞く。
その観点ではビルド速度改善には説得力があるが、インタプリタは使いやすさを優先する場合により適した代案だ。
Juliaのアプローチが気に入っている。
完全なインタラクティブ環境で、インタプリタが実際にはコードをコンパイルしてすぐ実行する。
SBCLのようなCommon Lisp環境も同様だ。
実行速度とコンパイル速度は、極端に言えば独立している。
その間には「妥協可能な領域」があり、実行ファイルの性能を落とさずにコンパイラをより高速化できることもある。
ゲーム分野は特別なケースではないという主張だ。
これまでで最高のコンパイラ速度はTCC(Fabrice Bellardの作品)だ。
マルチスレッドや複雑な最適化なしでも圧倒的に速い。
リリースにはClangを使うが、TCCのコード生成性能も悪くない。
Delphiのほうがはるかに表現力が高く安全でありながら、非常に速いコンパイル速度を提供していると思う。
Goコンパイラもかなり速い。
DMDはCとDの両方をコンパイルできるうえ、「ゴールドスタンダード」だと思っていた。
TCCがC23をサポートしてくれるといいのだが。
何か一つが「真のゴールドスタンダード」というわけではない。
vlangも再コンパイルが数秒で終わるほど速く、Goのコンパイラも非常に速い。
ビルドキャッシュなど、再コンパイル自体を避ける技術もあるので、誰か一人の専売特許ではない。
zigでアプリをビルドする中で、インクリメンタルビルドへの満足度が高かった。
SQLite、luauなどさまざまなライブラリを使う単一の静的バイナリを、Goと同じくらい速くビルドできる。
ただし、現時点でもself-hostedコンパイラにはかなりバグが残っている。
例としてSQLiteではllvmを使う必要があり、関連Issueはここで確認できる。
ZigがBazelやBuck2のようなビルドシステムとうまく連携できるのか気になる。
Zigはビルドスクリプトがチューリング完全なので、この種のシステムではキャッシュやビルド自動化が難しくならないか心配だ。
Rustでbuild.rsなしで使えるライブラリがより好まれる理由と同じだ。
Zigライブラリにもカスタムビルドが多いのか気になる。
Zigのビルドスクリプトは完全にオプションだ。
build.zigなしでも個別のソースファイルを直接ビルド/実行できる。
GCCやClangが入るワークフローならどこにでもZigを組み込める。
参考までに、ZigはCコンパイラの代替としても動作する。
関連する記事
BazelとZigの連携例としてrules_zigを紹介。
実プロジェクトのZMLもこれを活用している。
ZMLプロジェクト
Zigのコンパイルとコード生成がTPDEと比べてどうなのか気になる。
LLVM -O0より10〜20倍速いとは言うが、限界はあるようだ。
Zigの戦略は大胆だと思う。
だが、いまだにLLVMバックエンドを使うのが正しいのかは気になる。
LLVMはコンパイル速度とプラットフォーム対応の面では競争力があるが、最適な機械語生成では依然として比肩するものがない。
ReleaseビルドにだけLLVMバックエンドを使い、debugビルドは対応プラットフォームに限ってself-hosted方式をデフォルトにしている。
デバッグビルドは実際のテスト過程で何度も繰り返しビルドするので、この方式のほうが合理的だ。
コンパイラ性能への執着には共感できない。
結局は最適化パス(インライン化、dead code削除など)とコンパイル時間のどちらかを選ぶトレードオフだ。
最適化のないコンパイラは線形に速くなるだけで、それ以上の最適化をしようとすれば、常に時間コストが大きくならざるを得ない。
「優れたアセンブリ出力」は実際には重要な論点ではない。
Proebsting's Lawによれば、コンパイラ技術の進歩はマシン性能の向上よりはるかに遅い。
簡単で高速な最適化だけでも実用上は十分だという結論になる。
LLVMも絶対的な最適化を実現するわけではなく、コンパイル速度との比較ではすぐ限界に突き当たる。
JNIでCライブラリをラップするJavaライブラリを作っているが、プラットフォーム別の動的ライブラリのビルドがあまりに煩雑なので、zigでマルチプラットフォームビルドを検討している。
単純なshimなら、最近のJavaではPanamaとjextractを使うほうがよいだろう。
Zigはヘッダ、libcソース、自前のLLVMをすべて内蔵しており、クロスコンパイルが本当に簡単だ。
本当に何も気にしなくていいレベルだ。
Ghosttyが今self-hosted x86_64バックエンドでビルドできない理由が気になる。
Zigコンパイラがバグでクラッシュする。
まだ新しい技術なので複雑だが、いずれ解決するだろう。
Ghosttyユーザーの大半はaarch64プラットフォームだ。