Mac mini M4 でモバイル CI を回すチームは、CPU グラフがまだ空いているように見えると並列 xcodebuild を上げがちだ——のちにキュー p95 が伸び、各プロセスは「CPU 40% 前後」と報告するのに首を傾げる。本 2026 マトリクスは コンパイル律速 レーンと シミュレータ/IO 律速 レーンを分離し、-destination がユニファイドメモリ上でどう衝突するかを整理し、オーケストレータ YAML の横に貼れる九ステップと数値ガードレールを示す。
並列シミュレータ worker とメモリ絞り込み、Mac mini M4 上の UI テストシャーディング と併読し、三種類の「並列つまみ」が同じ DRAM 予算を奪い合うのを避ける。ディスク成果物の伸びが並列そのものより速いときは、Xcode を責める前に ディスク保持マトリクス と突き合わせる。
Apple Silicon CI で CPU 使用率が嘘をつく理由
ユニファイドメモリは GPU、Neural Engine、CPU コントローラが同一物理プールを共有することを意味する。CoreSimulator のフレームバッファ合成や Metal パイプライン準備に時間を使うレーンは CPU が控えめでも、Swift モジュールのコンパイル帯域を兄弟ジョブから奪う。APFS のコピーオンライトは錯覚を増幅する:シミュレータデータセットの削除と再作成を繰り返すとメタデータ I/O が跳ね上がるのに、「CPU ビジー」チャートはほとんど動かない。
- 共有 CoreServices キャッシュ:複数 destination が同一ヘルパーデーモンとロック付きキャッシュに当たる。
- ブートストーム:四台同時起動は一見順調でも、SpringBoard ウォッチドッグが静かに 1 レーンを再起動する。
- Metal 競合:UI スナップショットや軽量 GPU テストでも、同一 GPU スライス上のシェーダキャッシュと競合する。
運用ルール:二つの destination が同一 udid 系と同一 DerivedData スライスを共有するなら、隔離ではなくレースを作っている。
マトリクス A — レーン種別と M4 メモリ層の安全並列度
| レーン種別 | 24 GB 層 | 36–48 GB 層 | メモ |
|---|---|---|---|
| Swift コンパイルのみ | 重負荷 2 | 重負荷 3 | ジョブごとの DerivedData ルートとセット。コンパイラフロントエンドのメモリスパイクに注意。 |
| ユニットテスト(UI なし) | 2 | 3 | シミュレータ割当は依然発生——純コンパイルと比べ「タダ」ではない。 |
| XCUITest + スクリーンショット | 1 | 2 | GPU とディスクが支配。フリート拡張はシャーディング記事に従う。 |
マトリクス B — 症状、ボトルネック、第一緩和策
| 症状 | 有力ボトルネック | 緩和 |
|---|---|---|
ランダムな DTXProxy 切断 |
シミュレータ IPC + メモリ圧力 | 並列 UI レーンを減らし、スイート間にシャットダウンフックを入れる。 |
| UI ジョブと同時にコンパイルが遅くなる | ユニファイドメモリ帯域 | ラベルでコンパイルと UI のプールを分割し、UI はオフピークへ。 |
| 空き 60% でもビルドが這う | APFS 断片化 / inode 攪乱 | 週次でジョブローカルボリュームをローテーション。巨大共有 DerivedData を避ける。 |
オーケストレータ向け数値ガードレール
- ホストあたり UI 上限:24 GB ユニファイドメモリ機では、プロファイルでブート後に少なくとも 6 GB の回収可能ヘッドルームが示されない限り、同時 XCUITest destination は 1。
- コンパイル扇出:シミュレータと混在させる場合、
-parallel-testing-enabledのワーカー数を物理性能コアの 半分 に抑える。 - ディスク水位:CI ボリュームの APFS 空きが 18% を下回ったら新規並列 destination を停止——その下では CoreSimulator 作成が曖昧に失敗する。
容量リンク:マトリクスが 1 ホストの限界を示したら、ディスク増設だけを考える前に プール容量 SLO に並列を写し取る。
コードレビューを通る起動パターン
xcodebuild 引数を API 契約として扱う:小さなシェル関数や YAML アンカーでラップし、すべてのレーンが -derivedDataPath、-clonedSourcePackagesDirPath、-resultBundlePath を一貫設定する。ワンオフコマンドを CI に貼ると飽和回帰と設定ドリフトの相関が失われる。金曜夜と月曜朝で解決が変わるシェルグロブより、リポジトリに明示 -destination ブロックを置く。
マトリクスビルドは destination 名だけでなくテストクラスやターゲットでシャードし、各シャードが予測可能な GPU/IO シグネチャを持つようにする。シャードマニフェストと成果物アップロード方針を組み合わせ、失敗シャードがクリーンアップ前に xcresult を出せるようにする。オーケストレータが性能コア数と熱状態を出せるならログへ。持続的な並列 UI 負荷では macOS が静かにスロットルする——遠隔ではファンが穏やかでも。
中央キャッシュ:加速剤か IO 爆弾か
共有 DerivedData や SwiftPM キャッシュはコールドビルドを短縮するが、数十ジョブが同一ツリーにメタデータ書き込みを直列化する。集中キャッシュなら高速 APFS と inode 余裕、ジョブごとのサブフォルダ強制でロックストームをチーム横断させない。ヒット率と明示的な退避ポリシーをセットに——古い module map はミスより厄介で、手動クリアまで謎失敗が続く。
キャッシュがネットワークストレージにあると遅延スパイクが「flaky シミュレータ」に見える。ホットパスはローカル NVMe を優先し、成果物は非同期複製。どのレーンが共有キャッシュを読み書きできるか、読み取り専用ミラーかを文書化し、セキュリティレビューを追跡可能にする。
ビルドプラットフォーム担当者の九ステップ
- レーンにタグ:コンパイル/ユニット/UI をオーケストレータメタデータに——魔数より人間可読ラベル。
- destination を固定:CI ではシミュレータ UDID を明示。「最新 iPhone」名だけにしない。
- 週次で壁時計を測定:レーン種別ごと。回帰は赤ビルドより先に来る。
- DerivedData をレーン ID で隔離:マトリクス間の暗黙共有を禁止。
- Metal 負荷を上限:UI テストが動画を取るときは必要なら直列化。
- シミュレータ自動停止:各シャード後。消去周期は絞り込み記事に合わせる。
- IO 遅延アラート:カーネルカウンタが無い場合はステップ時間分散などの代理指標。
- エスカレーション文書化:スイート途中の TCC プロンプトは VNC でスポット確認。
- 地理的スケール:非互換レーンを 1 台のヒーローランナーに積む前に 香港・日本・韓国・シンガポール・米国 へ分散。
FAQ
xcodebuild -parallelizeTargets は CI に効くか?
マルチターゲットでは時に有効だがコンパイラフロントエンドを積む——共有ホストで全域有効化する前に計測する。
安定のためランタイムを古く固定すべきか?
再現性のため固定は合理的だが Apple のサポート終了日を追跡し、Xcode ピンとともにアップグレード窓を文書化する。
並列 xcodebuild destination を適正化することが Apple Silicon M4 CI を正直に保つ方法——チップは速いが ユニファイドメモリ と APFS メタデータが単純並列を隠れたキューに変える。ネイティブ macOS で SSH 優先の自動化と、詰まったシミュレータ向けの任意 VNC は本気のモバイル遠隔ラボの型に合う。香港・日本・韓国・シンガポール・米国 で 専用 Mac mini M4 をレンタル すれば CapEx なしに重コンパイルと重 UI を分離でき、同一ディスクへの非互換積み上げより 物理隔離 が勝る。争いが見えたら 料金 を開いてホストを足し、並列つまみを盲信しない。