Gosuke Miyashita archive notes about feed

生成AIにRustで開発させるときのPC負荷対策

2 November 2026

Carina の開発は、ほぼClaude CodeとCodexにやらせている。リポジトリはcarina本体、aws、awscc、registry、github、infraの6つに分かれていて、それぞれのリポジトリで、複数のエージェントがworktreeを切って並行に作業する。

Rustのプロジェクトでこれをやると、worktreeの数だけ target ができて、そのどれもがビルドとテストを回す。ここ数日、PCが重い、ディスクが足りない、という状態になっていたので、やった対策をまとめておく。雑なメモです。

ちなみにマシンはMacBook Pro(Apple M4 Max、16コア(Performance 12、Efficiency 4)、メモリ128GB、SSD 2TB)。それなりに良いマシンのはずなんだけど、それでも重くなるし、ディスクも足りなくなる。


ディスク容量

ビルド出力専用のボリュームを作った

APFSのボリューム /Volumes/CargoTargets を作って、Time Machineの対象から外した。

以前は、ビルド出力を消しても空きがなかなか戻らなかった。消した分がTime Machineのローカルスナップショットに残っていたため。専用のボリュームにして対象から外したので、消せばすぐに空きが戻る。

worktreeの target をそのボリュームへ向けた

~/.local/bin/link-cargo-target というスクリプトを書いて、worktreeの target を /Volumes/CargoTargets/<repo>/<worktree> へのシンボリックリンクにしている。メインのチェックアウトは対象外。

#!/bin/sh
# Point a linked worktree's `target/` at /Volumes/CargoTargets, a volume
# excluded from Time Machine, so deleted build output frees disk space
# immediately instead of lingering in local snapshots.
# Usage: link-cargo-target [worktree-dir]   (called from post-checkout hooks)
set -eu
vol=/Volumes/CargoTargets
wt=$(cd "${1:-.}" && git rev-parse --show-toplevel)
# Only linked worktrees; the main checkout keeps its own target/.
[ "$(git -C "$wt" rev-parse --git-dir)" != "$(git -C "$wt" rev-parse --git-common-dir)" ] || exit 0
[ -d "$vol" ] || exit 0
[ -e "$wt/target" ] || [ -L "$wt/target" ] && exit 0
repo=$(basename "$(dirname "$(git -C "$wt" rev-parse --path-format=absolute --git-common-dir)")")
dest="$vol/$repo/$(basename "$wt")"
mkdir -p "$dest"
ln -s "$dest" "$wt/target"

--git-dir と --git-common-dir が違えば、追加で作ったworktreeだと判断できる。メインのチェックアウトでは両方とも同じ .git を指すので、何もせずに抜ける。

infraを除くRustの5つのリポジトリで、post-checkoutフックからこれを呼ぶ。

#!/bin/sh
[ "$3" = 1 ] && ~/.local/bin/link-cargo-target . || true

post-checkoutフックの3番目の引数は、ブランチのチェックアウトなら 1 、ファイル単位のチェックアウトなら 0 になる。 git worktree add でもこのフックが走るので、worktreeを作った時点でシンボリックリンクができる。

ひとつハマりどころがあって、 .gitignore の /target/ はシンボリックリンクには一致しない。末尾に / が付いたパターンはディレクトリにしか一致しないため。なので、5つのリポジトリすべてで .git/info/exclude に /target を足した。

消したworktreeのビルド出力を掃除する

worktreeを消しても、 /Volumes/CargoTargets 側のビルド出力は残る。なので、worktreeがもう存在しない /Volumes/CargoTargets/<repo>/<worktree> を消す ~/.local/bin/prune-cargo-targets を書いた。

#!/bin/sh
# Delete per-worktree build dirs on /Volumes/CargoTargets whose worktree no longer exists.
# link-cargo-target creates <vol>/<repo>/<worktree>; removing the worktree leaves it behind.
set -eu
vol=/Volumes/CargoTargets
src=$HOME/src/github.com/carina-rs
[ -d "$vol" ] || exit 0
for d in "$vol"/*/*; do
  [ -d "$d" ] || continue
  repo=$(basename "$(dirname "$d")")
  wt=$(basename "$d")
  [ -e "$src/$repo/.worktrees/$wt" ] && continue
  echo "removing $d"
  rm -rf "$d"
done

worktreeはどのリポジトリでも .worktrees/ の下に作る決まりにしているので、そこに同じ名前のディレクトリがなければ消す、というだけのもの。

これは、PRとCIを見張るスクリプト( ~/.local/bin/carina-watch )から、30分ごとに呼ぶようにしている。初めて回したときは134GBが空いて、空きが206GiBから334GiBになった。

Codexの書き込み先

Codexのサンドボックスから /Volumes 以下に書き込むには、 --add-dir /Volumes/CargoTargets を付ける必要がある。


PCが重い

Microsoft Defenderのシステム拡張を立ち上げ直した

いちばん効いたのがこれ。

sudo killall epsext

Defenderのリアルタイム保護はオフにしてあるのに、 epsext が5日間、CPUを1コア分(約100%)使い続けていた。立ち上げ直したら、CPUは2.7%、メモリは2.9GBから16MBになった。

ついでにDefenderの除外に、CargoTargets、 ~/Library/Caches/Mozilla.sccache 、 ~/.cargo 、 ~/.rustup の4つを足してみたけど、これは効かなかった。リアルタイム保護がもともとオフなので、当然と言えば当然。

ただ、一連の対策を終えたあとで、リアルタイム保護はオンに戻した。なので、この除外は今はちゃんと意味を持っている( ~/src/ は前から除外済み)。

CargoTargetsのSpotlight索引を止めた

sudo mdutil -i off /Volumes/CargoTargets

ビルド出力をSpotlightが索引に入れていたので、止めた。


ビルドの負荷を抑える運用

ここからは、エージェントに守らせているルール。


また重くなったときに見るもの

uptime
ps -Ao pcpu,rss,command -r | head
ps -o etime=,pcpu= -p $(pgrep -f epsext | head -1)
df -g /