ビルドキャッシュとプリコンパイル済み stdlib
主要なコンパイル言語の多くは標準ライブラリを毎回コンパイルし直さない。やり方は言語ごとに違うが、本質は共通で「コンパイル結果を事前に作って再利用する」。レイヤーは2つ — ①配布時にプリコンパイルした stdlib、②ユーザービルド時のキャッシュ(インクリメンタル)。
なぜ要るか
コンパイルは高コストな一方、stdlib は不変で全ユーザー共通。毎回ソースから作り直すのは純粋な無駄。そこで「一度作った成果物を、入力が変わらなければ再利用する」。多くのビルドシステムは 入力ハッシュ → 生成物 のマップとしてキャッシュを持つ。
言語別の仕組み
| 言語 | プリコンパイル済み stdlib | ビルドキャッシュ / インクリメンタル |
|---|---|---|
| Rust | core/std を .rlib でツールチェーンに同梱 → リンクのみ | cargo が依存クレートを target/ にビルドキャッシュ、未変更なら再利用 |
| Go | 標準ライブラリをプリビルドの .a アーカイブで配布 | $GOCACHE に「入力ハッシュ→生成物」を保存、同入力なら再コンパイルをスキップ |
| C / C++ | libc / libstdc++ 等のプリコンパイル済み共有/静的ライブラリをリンク | ヘッダ再パース削減に PCH(プリコンパイル済みヘッダ)、C++20 は モジュールの BMI(バイナリインターフェース)キャッシュ |
| OCaml / Haskell (GHC) | — | コンパイル済みインターフェース(.cmi / .hi)+オブジェクトをキャッシュし、未変更モジュールは再コンパイルしない |
| Java / .NET | stdlib をバイトコード / アセンブリで配布(そもそも毎回コンパイルしない) | JIT/AOT が別途、最適化結果のキャッシュを持つ |
2つのレイヤーを分けて理解する
- 配布時プリコンパイル — stdlib のように「不変・共通」なものは、ツールチェーン/ランタイムに成果物として同梱(
.rlib/.a/libc/ バイトコード)。ユーザーはリンク or ロードするだけ。 - ビルド時キャッシュ(インクリメンタル) — ユーザーのコードや依存は変わりうるので、入力ハッシュをキーに生成物をキャッシュし、差分だけ再ビルド(cargo
target/、$GOCACHE、.cmi/.hi)。
両者とも狙いは同じ — 「同じ入力からは同じ出力 → なら作り直さない」(参照透過なビルド)。
押さえどころ(カード化候補)
- 本質 → コンパイル言語は stdlib を毎回コンパイルしない。「コンパイル結果を事前に作って再利用する」が共通原理。
- 2レイヤー → ①配布時プリコンパイル(
.rlib/.a/libc/バイトコード=リンク・ロードのみ)②ビルド時キャッシュ(入力ハッシュ→生成物、差分再ビルド)。 - Rust → stdlib は
.rlib同梱、cargo がtarget/にインクリメンタルキャッシュ。 - Go → stdlib は
.a、$GOCACHEが入力ハッシュで再コンパイルをスキップ。 - C++ の今 → PCH に加え C++20 モジュールの BMI キャッシュでヘッダ再パースを削減。
- GHC/OCaml →
.hi/.cmi(インターフェース)+オブジェクトで未変更モジュールを再コンパイルしない。
関連
- rust —
.rlib同梱 + cargo のtarget/インクリメンタルキャッシュ - almide — マルチターゲット codegen。stdlib をどう供給/キャッシュするかは同じ設計問題
- dead-code-elimination — リンク時に未使用 stdlib を削る最適化(プリコンパイルと併走)
- copy-on-write — 「変わるまで作り直さない」という遅延・再利用の発想は同根
- deterministic-codegen — 「同じ入力→同じ出力」が成立しないとキャッシュは無効化する。その前提