
2026/09/12 23:45
Bun のコンパイル時間を理解するためのビルド可視化工具を作成しました
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
著者はbuildprofを開発した。これは、ソフトウェアのコンパイル時間をプロファイリングおよび可視化するためのオープンソース Linux トレースツールであり、バーの幅が持続時間を示し、左から右への移動が時間の流れを示す視覚的時間線(visual timelines)を生成するものである。この革新は、
strace や ninjatracing などの既存ツールの不足を解消するものであり、これらのツールは複雑なサブプロセスツリーや特定のビルドワークフローを捕捉できないことがよくある。プロジェクトの動機は、Bun 1.4.0 の Rust ビルドがその Zig 前身よりも大幅にパフォーマンスを発揮しているという観察に基づいていた。6 コア VM でのベンチマークでは、Zig エポックの Full LTO ビルドには約 24 分かかり、外部の JavaScriptCore リンカーによって支配的であり、Rust ThinLTO ビルドは 90 以上の内部 crate 間の並列処理によりわずか約 5 分しかかからなかった。プロファイリングの結果、Bun のコード自体を Full LTO から ThinLTO に切り替えるだけでは僅かな向上(約 4 分の削減)しか得られなかったが、外部依存項(WebKit と ICU)を互換性のある ThinLTO 設定で再ビルドすることで逐次的なボトルネックを解消し、全体時間を劇的に削減できた。技術的には、buildprof はプロセス記録には ptrace、ファイルシステムインターセプションには seccomp フィルターを利用しており、カスタマイズされた Perfetto UI 内で直接依存関係のリンクを明確に可視化することが可能である。本文
buildprof: ビルド時間の謎を可視化するオープンソース・プロファイラー
buildprof(GitHub)は、Linux 環境下でソフトウェアをコンパイルする際に時間をどこに費やしているかを示すためのオープンソースのトレーシングツールです。
buildprof を使用することで、クリーンビルドの実況を可視化し、並列化の効率不良や重複処理、依存関係のダウンロード遅延、巨大なコンパイラ/リンカーへの呼び出しなどの問題箇所を明確に特定できます。
使用方法
非常に簡単です。既存のビルドコマンドの前に
を付けるだけです。buildprof --
buildprof -- make -j16buildprof -- cargo buildbuildprof -- ninja -C out/targetbuildprof -- just buildbuildprof -- ./dev/custom-build-script.sh
動作原理
ビルドコマンドが起動するすべてのプロセス(およびそのサブプロセス)を記録し、一つのタイムライン上に配置します。
- 時間: 左から右へ進みます
- バーの幅: 実行期間を示します
- 親子関係: 子プロセスは親プロセスの直下に配置されます
開発動機:Bun のビルド速度への疑問
開発のきっかけは、Bun JavaScript runtime の首席アーキテクトである Jarred Sumner 氏のツイートに対する疑問です。
当初の疑惑
- 主張: 「Bun の新しい Rust ベースのビルドは、旧来の Zig ベースのビルドよりも5 倍以上高速である」
- 直感: 通常、同様の複雑さを有する場合、Zig プロジェクトの方が Rust よりも速くコンパイルされる傾向があります。
- 疑問: この性能差の真の要因は何なのか?
重要な背景情報 (LTO)
ツイートの詳細には以下の点が含まれていました。
- Zig ベース: Full LTO(リンク時間最適化)を使用
- Rust ベース: ThinLTO を使用
- 技術的差:
はコンパイルユニットを一つの巨大な最適化タスクに統合します。Full LTO
は並列実行を可能にするため、より分離性が高く、効率的です。ThinLTO
検証と調査:24 分 vs 5 分の実績
まず数値の再現を試みました(6 コア 12 スレッドの Linux VM)。結果は Jarred 氏の発表とほぼ一致しました。
| 項目 | Zig エポック (報告/実測) | Rust エポック (報告/実測) |
|---|---|---|
| CI メジアン | 30m06s / 24m24s | 5m37s / 5m40s |
問題の所在:なぜ Zig は遅いのか?
- 仮説: Zig コンパイラ単体の遅さか、Full LTO のリンク処理か、それとも他の要因か?
- 解決策: プロセスツリーを可視化することで、「いつ」「何が」「どのくらい」時間を費やしたかを追跡しました。
ビルドのプロセスツリーと可視化の利点
ビルドコマンドは単なるプログラムの実行ではなく、複数のツールが連鎖的に作業を行うプロセスツリーです。
プロセスツリーの構造例 (Rust)
cargo └── rustc └── cc └── collect2 └── ld.lld
buildprof の視覚化機能
- ビルドシステム非依存: Cargo、Ninja、Make などに対応(プロセス起動を共通認識するため)。
- カスタムスクリプトの追跡: 上位スクリプト(セットアップ系)と下位スクリプト(ジェネレーター系)の両方を可視化。
- ファイル活動の追跡: プロセス間のファイル入出力(読み書き)を記録し、矢印で連結関係をグラフ化できます。
Bun の CI ビルドにおけるボトルネックの特定
1. Zig エポック:リンカーが時間を支配している
buildprof で Zig ベースのビルドを記録したところ、ld.lld リンカーへの呼び出しが問題であることを突き止めました。
- 時間: 単独で 16 分以上稼働し、全体時間の約 2/3 を占めていました。
- 要因: Full LTO のリンク処理です。リンカーは単なる結合ではなく、プログラム全体に対するコンパイラのパスを実行しています。
改善策:ThinLTO に切り替え
--compiler-traces オプションを有効にし、LLD の内部イベントを追跡した結果、時間配分が明確になりました。
- 効果: Link-Time Optimization の設定変更により、リンク時間が大幅に短縮されました。
2. WebKit ライブラリの問題
Rust エポックでは LTO が速いですが、Zig エポックでも同様の課題がありました。
- 原因: Bun が使用する JavaScript エンジン(JavaScriptCore/WebKit)のライブラリ自体が、別のビルドからダウンロードされており、
(Full LTO) でコンパイルされていたためです。-flto=full - 対策: WebKit リポジトリを再構築し、互換性のある ThinLTO 設定 に変更しました。
3. 最終的な改善結果
以下の手順(Zig の Full LTO → ThinLTO 変更 + WebKit/ICU の再ビルド)を実施した結果です。
| ビルド構成 | 全体時間 (F/L) | ファイナルリンカー時間 |
|---|---|---|
| オリジナル Zig (Full LTO) | 24m24s | 16m35s |
| ThinLTO + オリジナル WebKit | 20m20s | 12m55s |
| ThinLTO + 再構築 WebKit | 15m11s | 7m22s |
4. Rust vs Zig の構造的な差
リンク処理以外の遅延要因も特定されました。
- Rust: コードを 90 クレイスト(モジュール)以上 に分割し、並列化してコンパイルしています。
- Zig: ビルド全体を 1 つの巨大な Zig モジュール として処理しようとしました。
- 結果: Rust は並列化できるため速いですが、Zig は単一モジュールによるリンク処理自体がコスト高でした。
まとめ
初期ビルドでの大きな遅延は、巨大なリンカーステップ(Full LTO と WebKit の影響) に起因していました。これを解決することで、ビルド時間は 24 分から 15 分 に短縮されました。ただし、残りの 7 分のリンク処理と構造的な並列化の不足が依然として差を生んでいます。
他にも見つけた「小さな遅延」
ビルドトレースからは、最適化対象ではないものの興味深い発見もありました。
- ネットワーク通信: IP アドレス取得などのパブリックインターネットへの問い合わせ(<1 秒)。
- 依存関係のダウンロード: WebKit のアーカイブ ダウンロードと抽出に約 20 秒かかった場合がある。
- コンパイラの内部: Clang の
一つで、バックエンド作業の 4 秒以上を消費しているケースもある。ModuleInlinerWrapperPass
buildprof の仕組み
技術スタック
- ptrace: Linux 固有のデバッガーインターフェースを使用。子プロセスのフォークや
を直接追跡可能。exec - seccomp: ファイルシステム活動において、必要な呼び出しのみをインターセプトしてオーバーヘッドを抑制します。
- UI: Perfetto UI のソフトフォークです。録画データをブラウザで開き、プロセスツリーやファイルの流れを可視化できます。
オーバーヘッドの例
| 対象 | 未追跡 (秒) | 追跡時 (秒) | オーバヘッド |
|---|---|---|---|
| ripgrep / Cargo | 12.27 | 12.30 | 僅か |
| Redis / Make | 26.78 | 31.89* | 約 5 秒 (ファイル操作多) |
*ファイルシステム追跡をオフにするには
オプションを使用します。--no-file-events
類似ツールとの比較
既存のツールの限界が明らかになり、buildprof を開発した動機となりました。
| ツール名 | 特徴・制限点 |
|---|---|
| ninjatracing | Ninja ログのみ解析。サブプロセス全体や上位スクリプトを一つのブロックとして表示するため、詳細が見えない。 |
| Cargo timings | Cargo 管理部分しか見えず、ビルドスクリプト全体(例:Bun の 5m40s のうち 1m51s)をカバーできない。 |
| -ftime-trace (Clang) | コンパイラ内部の詳細はわかるが、ビルド全体の文脈やコンパイラ自体の理解には不十分。 |
| strace / tracexec | プロセスイベント全体を表示するが、ビルド特有の依存関係や順序の可視化がない。 |
| What The Fork (via) | プロセス追跡に近いが、現在プライベートベータ版でオープンソース化なし。 |
今後の計画と結論
buildprof はすでに期待した成果を上げましたが、以下の点での改善を目指しています。
- 記録オーバーヘッドの低減: 特に多数のファイルを扱うビルド(Redis など)への最適化。
- OS 拡張: macOS への対応検討、Windows へのサポート要望受付。
- より多くのツールチェーン対応: npm、Gradle、Bazel などのテスト実施。
- クリティカルパスの自動特定: 手動での依存関係追跡から、ビルドを妨げるチェーンを自動的に注釈付ける機能へ。
結論:
buildprof は「ビルドが遅い理由」を可視化し、根本原因(例:LTO 設定、外部ライブラリのコンパイル方法)へのアプローチを可能にしました。次回ビルドが遅くなった際は、ぜひ
buildprof を使って原因を探ってみてください!