# AI 開発におけるコンピューター性能コストの最適化 · Rust 編

## Rust：複数 worktree にそれぞれ同じものをコンパイルさせない

AI の{並列開発|へいれつかいはつ}で最も{高|たか}くつくコストは、モデルではなく、**Rust が{依存関係|いぞんかんけい}を{繰|く}り{返|かえ}し{再|さい}コンパイルすること**である{場合|ばあい}がよくあります。

> **{現場|げんば}**：8 コアの Mac で、二つの worktree が同時に `cargo test` を実行している。問題は「二つの cargo」そのものではなく、**各 cargo がデフォルトで論理コア数に合わせてコンパイラを起動すること**です。十数個の `rustc` が一斉に走り、マシンは激しく swap し始めます。コンパイル自体は落ちていなくても、コンピューターはもう普通に作業できません。

四つの対策は、すべて必要です。

### 1. 並行数を制限する：二つの軸、どちらも必須

多くの人は半分だけ制限しています。「同時にいくつの cargo を走らせるか」だけを制限し、各 cargo 自身がさらに fan-out することを忘れています。

- `sem` で**同時にいくつの cargo を走らせるか**を制限する
- [`CARGO_BUILD_JOBS`](https://doc.rust-lang.org/cargo/reference/config.html) で**各 cargo がいくつの rustc を起動するか**を制限する。これがないと、各 cargo は論理コア数に合わせて並行実行します。これこそが「2 個の cargo が 15 個の rustc になる」本当の原因です。

wrapper（`heavy-cargo`）：

```sh
#!/bin/sh
# 8コア/16GB：同時に重い cargo は一つ、各 cargo は最大二つの rustc。
# コア数だけでなく swap を見て調整する。32GB+ なら sem -j 2 を試せる。
exec sem --id rust-heavy -j 1 --fg -- \
  env RUSTC_WRAPPER=sccache CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 \
  nice -n 15 taskpolicy -b cargo "$@"
```

`nice` は CPU 優先度を下げます。`taskpolicy -b` は Darwin のバックグラウンドスケジューリング方針を設定します（バックグラウンドタスクの CPU/I-O を抑える）。この二つは Linux の `ionice` と**厳密には等価ではなく**、同じ目的のためのプラットフォーム別手段です。

### 2. コンパイルキャッシュを共有する（sccache）

[sccache](https://github.com/mozilla/sccache) により、異なる worktree 間でコンパイル生成物を再利用できます。鍵になるのは incremental です。

- `.cargo/config.toml` の `[env] CARGO_INCREMENTAL = "0"` は**効きません**。`[env]` は cargo が起動する子プロセスに変数を渡すだけで、cargo 自身の増分コンパイル判断に影響するには間に合いません。
- ただし、**起動前に** `CARGO_INCREMENTAL=0 cargo …` を設定すれば有効です（上の wrapper はこの方法です）。利点は、「incremental を切る」対象を重いビルドに限定できることです。他のプロジェクトは通常どおり増分コンパイルでき、全体で犠牲にする必要はありません。全体で無効化したいなら、[`[build] incremental = false`](https://doc.rust-lang.org/cargo/reference/config.html) を使えます。

worktree をまたいでヒットしやすいのは主に**サードパーティ依存関係**です（同じ registry、同じパス）。自前の crate は**デフォルトでは**絶対パスがキャッシュキーに入るため、worktree をまたいでヒットしません。ただし新しい sccache の `SCCACHE_BASEDIRS` は、異なる checkout のパスを正規化できます。

```sh
SCCACHE_BASEDIRS="$PWD" RUSTC_WRAPPER=sccache CARGO_INCREMENTAL=0 cargo check
```

100% ヒットすると約束してはいけません。`bin`、`dylib`、`cdylib`、`proc-macro` のようにリンカを呼ぶ crate は、そもそもキャッシュできません。

**本当に効いているか必ず検証してください**。そうしないと、設定しても有効かどうか分かりません。

```sh
sccache --zero-stats
heavy-cargo test -p server
sccache --show-stats     # cache hits / requests executed を見る
```

### 3. target はそれぞれ独立させ、共有しない

容量を節約しようとして、複数の worktree を同じ `CARGO_TARGET_DIR` に向けてはいけません。ロック競合が起き、かえって遅くなります。**再利用はキャッシュで行い、target の共有では行いません。**

### 4. さらに速くしたい場合（プラットフォームごとに検証）

- **リンカ**：Linux では [mold](https://github.com/rui314/mold) / lld を試せます（リンク段階が時間の半分を食うことはよくあります）。**macOS に mold をそのまま適用してはいけません**。mold の Rust 設定は明確に `target_os = "linux"` に限定されており、README の Apple M1 テストは Fedora Asahi 上で実行されたもので、macOS ではありません。リンカを替える場合は、対象プラットフォームごとに個別に検証する必要があります。
- [cargo-nextest](https://nexte.st/) でテストを実行する。dev profile で `debug = "line-tables-only"` にし、デバッグ情報のサイズを削る。

### 最後：制約を AGENTS.md に固定する

ツールを入れても、agent が相変わらず素の `cargo test` を実行するなら、流量制限は実装されていないのと同じです。次の一文を固定してください。

```text
重い Rust build/test は必ず heavy-cargo 経由で実行すること；
agent は複数の cargo test を直接並行起動してはならない。
```

一言で言えば、**並行数を制限し（二つの軸で）、キャッシュを共有し、target を分離し、プラットフォームごとにリンカを選ぶ**ことです。さらにそのルールを `AGENTS.md` に固定すれば、AI に同時に三つの作業ラインを開かせても、コンピューターは耐えられます。
