世界を動かす技術を、日本語で。

Zigのビルドがより速くなっています

概要

  • Zig 0.15.1 の登場で、コンパイル時間が大幅短縮。
  • Ghosttyプロジェクト での実測値をもとに、各ビルド工程の高速化を紹介。
  • まだ LLVM依存 が残るものの、今後の自前バックエンドでさらなる短縮が期待。
  • インクリメンタルコンパイル の本格対応で、将来的にはミリ秒単位のビルドも現実に。
  • Zigの進化が開発体験を大きく改善している現状を報告。

Zig 0.15.1によるビルド時間の劇的改善

  • Andrew Kelleyの「 コンパイラが遅いからバグが増える」という発言がZig開発の原動力。
  • Zigチームは LLVM排除、独自バックエンドやリンカの開発、インクリメンタルコンパイル対応などに注力。
  • Zig 0.15.1 でその成果が顕著に現れ始めた。

ビルドスクリプトのコンパイル時間比較

  • Zig 0.14 :7秒167ミリ秒
  • Zig 0.15 :1秒702ミリ秒
    • zig build --help実行時の計測値。
    • 新規ユーザーや初回ビルド時の体感速度に直結。

Ghosttyバイナリのフルビルド時間

  • Zig 0.14 :41秒
  • Zig 0.15 :32秒
    • ビルドスクリプトのビルド時間も含む。
    • 依然として LLVM依存 だが、Zigコンパイラ自体の改善が明確。

インクリメンタルビルド(Ghostty実行ファイル)

  • Zig 0.14 :19秒
  • Zig 0.15 :16秒
    • コア部分の1行修正後の再ビルド時間。
    • 依然 LLVM利用、インクリメンタルコンパイル未対応のため再ビルド範囲は広い。
    • LLVM排除時は約12秒を予想、インクリメンタル対応後は ミリ秒単位 も視野。

インクリメンタルビルド(libghostty-vt)

  • Zig 0.14 :2秒884ミリ秒
  • Zig 0.15 :975ミリ秒
    • 1行修正後のライブラリ単体ビルド。
    • 自前x86_64バックエンド でLLVM非依存。
    • インクリメンタルコンパイル対応時は 1ケタミリ秒台 も期待。
    • 現状でもサブセカンドでのビルドが実現。

開発体験の変化

  • Zig 0.15.1移行後、 ビルドやテストの待ち時間が激減
  • 以前はビルド中に別作業をしていたが、今は シームレスな開発フロー を実感。
  • x86_64バックエンド はデバッグビルドで安定、 aarch64 も進化中。
  • まだGhostty全体の自前バックエンドビルドは未対応だが、数ヶ月以内の解決を予想。

今後の展望とまとめ

  • Zig 0.15.1 で全てのビルドケースが高速化。
  • まだ 自前バックエンドインクリメンタルコンパイル の恩恵を最大化できていない段階。
  • Zigの進化は 開発者体験の向上 に直結、今後数年で更なる高速化が見込まれる。
  • 現状の改善だけでも十分に価値があり、これからの進化に大きな期待。

参考情報

  • 計測は 同一x86_64 Linuxマシン で実施。
  • Andrew Kelleyの発言:https://youtu.be/5eL_LcxwwHg?t=565

Hackerたちの意見

これは大胆な戦略だね。うまくいくか見てみよう。でも、確かこれってLLVMのバックエンドを使ってるよね?コンパイル速度やサポートされてるプラットフォームの数ではLLVMに勝てるかもしれないけど、素晴らしいアセンブリを出力する能力に関してはほぼ無敵だよ。

コンパイラのパフォーマンスにこだわる理由がよくわからないな。結局、パフォーマンスの最適化(インライン化、デッドコードの排除、部分評価など)とコンパイル時間をトレードオフしてるだけじゃん。もしコンパイル時間を早くしたいなら、最適化パスのないコンパイラを使えばいいだけだよ。それでコード行数に対してコンパイル時間が線形になるし、これ以上の改善はできないって証明されてるからね。最適化の複雑さによっては、さらにパスを追加すると線形または超線形のオーバーヘッドがかかるし。

でも、素晴らしいアセンブリを出力する能力に関してはほぼ無敵だよ。 「プロエブスティングの法則:コンパイラの進化は18年ごとに計算能力を倍増させる」 つまり、簡単で明白な最適化をするのが「十分良い」ってことだよね。LLVMが最適化において神レベルだとしても、その最適化のコストはすぐに逆転しちゃうんだ。

リリースビルドにはLLVMのバックエンドを使ってるだけだよ。デバッグビルドでは、Zigは今やサポートされているプラットフォーム(今のところx86_64だけ)で自己ホストがデフォルトになってる。デバッグビルドはソフトウェアをテストする際のホットパスに直接関わるから(テストバイナリのビルドも含む)、デバッグビルドの反復回数はリリースビルドよりも桁違いに多いから、これは良いトレードオフだね。

LLVMは罠だよ。すごく早くブートストラップできて、いろんな最適化パスやプラットフォームを無料で手に入れられるけど、最終的な最適化パスやリンク段階のパフォーマンスを調整する能力を失うんだ。クレーンリフトがRustで急成長するのはすぐだと思うけど、初期の頃にLLVMにこだわってなかったら、あんなに強力な言語にはなってなかったと思う。Goはずっと前にコード生成やリンクのアウトソーシングを避ける選択をして、うまくいってるみたいだね。

え、リリースビルドにcraneliftを使う予定ってあるの?

それはLLVMをそのまま使うんじゃなくて、フォークするのが正しいからだよ。編集: 笑 @ダウンボート。みんな、真剣なコンパイラ組織がどう運営されてるか分かってないの?AppleやIntel、AMD、NVがポイントリリースをインストールして終わりだと思ってるの?

GoとOcamlは、めっちゃ速いコンパイラを持ってるよね。最初からちゃんと作り込んであって、今はその代償を払わなくて済む。もう1分以上かかるコンパイル時間のシステムで作業する自分が想像できないよ(プロダクションビルドは除く)。もっと多くのプロジェクトが、自分たちの「開発用」コンパイラを持って、llvmがやるような余計なことはせず、最終的なプロダクションビルドのためだけにllvmを使うといいのに。

言語が移行できるなら、それが罠ってどういうこと?

さらに、C++の代替になりたい言語は、LLVMがツールインフラの一部である限り、C++に依存し続けるんだよね。最初の導入の壁を越えたら、プログラミング言語は自立すべきだと思う。そうすれば、CやC++が常に必要だっていう都市伝説も減るし。実際、多くの場合は便利さのために使ってるだけで、使えるコンパイラを書くのには時間がかかるから、特に複数のプラットフォームに移植する時には簡単な方法なんだよね。でも、それが魔法の道具ってわけじゃなくて、コンパイラを書くのが不可能になるわけじゃない。ここはGoチームの決定に完全に賛成だな。

コンパイラの速度のゴールドスタンダードはTCCだよ。(ファブリス・ベラール) それほど最適化されてるわけじゃない(シングルスレッドで、特別なトリックもなし)けど、今のところ大きな差で負けてないんだ。もちろん、リリースにはClangを使ってるけど、tccのコード生成はひどくもないよ。

Goのコンパイラもかなり速いよね。

特に「ゴールドスタンダード」ってのはないよ。vlangはめっちゃ速くて、数秒で全部再コンパイルできるし、Goのコンパイラもやることに対してはかなり速い。ビルド効率に関しては、どのプラットフォームも独占してるわけじゃないし、再コンパイルが必要ないビルドキャッシュツールやテクニックもあるからね。

コンパイラの速度のゴールドスタンダードはTCCだね。ただ、C23をサポートしてくれればいいのに…

CとDの両方をコンパイルできるDMDがゴールドスタンダードだと思ってたんだけど!?

Delphiの方が、もっと表現力があって、安全な言語だと思うし、コンパイル速度もめちゃくちゃ速いから、そっちを推したいな。

BazelやBuck2みたいなポリグロットビルドシステムでZigをビルドするのは簡単な道筋があるのかな?Zigがチューリング完全なビルドスクリプトに依存してるせいで、そういう決定論的なシステムでのビルド(とキャッシュ)が難しくなるんじゃないかと心配してる。Rustでは、build.rsを避けるライブラリの方がずっと好まれる理由がこれだよね。Zigのライブラリは通常、カスタムビルドのセットアップが多いのかな?

bazelについて: https://github.com/aherrmann/rules_zig ZMLみたいな実際のプロジェクトが使ってるよ: https://github.com/zml/zml

zigのインクリメンタルビルドには本当に感謝してる。単一の静的バイナリがあるのが好きで、SQLiteやluauみたいなライブラリをいくつか使ってるアプリがあって、Goの速度に近い感じでコンパイルできるんだ。ただ、自己ホスティングのコンパイラにはまだバグがあるのは確か。例えば、SQLiteを使うにはllvmを使わなきゃいけなくて、これがちょっと面倒なんだよね。 - https://github.com/vrischmann/zig-sqlite/issues/195

Ghosttyを自己ホスティングのx86_64バックエンドでビルドするのを何が正確に妨げてるのか知ってる人いる?

まあ、ほとんどのユーザーはaarch64だよね。

ちょっと脱線するけど、CライブラリへのJNIシムみたいなJavaライブラリを書いてるんだ。プラットフォームやアーキテクチャの組み合わせごとにダイナミックライブラリをビルドするのが大変そうで、zigコンパイラを使ってコンパイルやクロスコンパイルをやってしまおうか考えてるところ。

ghosttyに対する唯一の願いは、OpenBSDのサポートがあればいいなってこと。OpenBSDのkqueueをサポートするために、IOレイヤーでちょっと作業が必要みたい。

Zigの目標をもう少し理解しようとしてるんだけど… 開発が遅くなるからコンパイル速度が気になるなら、インタプリタに取り組むのが理にかなうんじゃない?もしかしたら単純すぎる考えかもしれないけど、コンパイルして実行速度を上げるのは、コンパイル時間とは本質的に無関係に思える。インタプリタを使えば、コードを簡単に計測できるし、ランタイムもコントロールできるから、たくさんの開発ツールの可能性があるよね。もちろん、特定のケースでは、フル最適化されたリリースバイナリだけをデバッグしたい人もいるから、彼らにとってはインタプリタやデバッグビルドに取り組むのは意味がないかもしれない。でも、それはほんの少数派だよ。そこでも、通常はすぐにコンパイルできるホットループを最適化してるしね。

実行速度のためのコンパイルは、コンパイル時間とは本質的に無関係に思える それは限界では正しいね。よくあることだけど、究極の実行速度と究極のコンパイラ速度の極端な間には、互いにトレードオフしない最適化ができる広いスペースがある。そこで、同じバイトコードを生成しながらコンパイラを速くすることができるんだ。

ゲームが特例だとは言えないと思う。

「Zigの目標をもう少し理解しようとしてるんだけど… 開発が遅くなるからコンパイル速度が大事なら、インタプリタに取り組む方が良くない?」って感じだよね。Rustは安全性、パフォーマンス、使いやすさの順で分類されてるのを聞いたことがあるけど、Zigはパフォーマンス、使いやすさ、安全性って感じ。そう考えると、ビルド速度の向上は納得できるけど、インタプリタは使いやすさが一番のトリプルにしか合わないよね。

僕にとって、Zigという言語は「まあまあ」だけど、Zigのツールチェーンはすごく興味深い。すごく野心的で、長期的には現実的じゃないと思うけど、成功してほしいな。