同じCIパイプラインでも、ある実行では18分で完了し、別の実行ではテスト段階で停滞した後、再実行すると正常に戻ることがあります。こうした挙動を、すぐに「マシンが一時的に遅くなった」と判断してはいけません。クラウドMacでは、コンパイラ、リンカ、シミュレータ、テストプロセスが同時にユニファイドメモリを奪い合います。ビルド結果だけでは、コードの問題なのか、並列数が多すぎるのか、それともシステムが継続的なメモリ圧縮とスワップの状態に入っているのかを判別できません。
再現可能な観測範囲を定義する
診断では、コミット、依存関係キャッシュの状態、ビルドコマンド、テスト対象を固定する必要があります。問題を安定して再現できるジョブを選び、開始時刻、終了時刻、終了コード、失敗した段階を記録します。最初の計測では、キャッシュの削除と並列数の変更を同時に行わないでください。どちらの変数が結果に影響したのか判断できなくなります。
まず、物理メモリとスワップ領域のベースラインを確認します。
sysctl -n hw.memsize
sysctl vm.swapusage
memory_pressure -Q
vm_stat
hw.memsize は物理メモリのバイト数です。vm.swapusage は現在のスワップ使用量を示します。vm_stat のページサイズ、圧縮ページ数、ページインおよびページアウトのカウンタは、推移を判断する材料になります。特定時点の値をしきい値として扱うのではなく、ジョブ開始前と終了後の増分を重視してください。
空きメモリが少ないからといって、メモリ不足とは限りません。macOSは利用可能なメモリを積極的にファイルページのキャッシュへ使用します。注目すべきなのは、メモリ圧迫が継続的に高まり、スワップ領域が増え続け、それと同時にビルド時間の増加やプロセスの異常終了が発生している状態です。
ビルド中に継続して計測する
診断コマンドはビルドコマンドの外側で実行し、子プロセスが終了しても記録が失われないようにします。次のスクリプトは、メモリ圧迫、スワップ使用量、RSS上位15プロセスを10秒ごとに保存します。
#!/bin/zsh
set -eu
out="${1:-memory-samples.log}"
while true; do
printf '
=== %s ===
' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >> "$out"
memory_pressure -Q >> "$out" 2>&1
sysctl vm.swapusage >> "$out" 2>&1
ps -axo pid,ppid,rss,etime,command | sort -nrk3 | head -n 16 >> "$out"
sleep 10
done
別のターミナルで先に zsh sample-memory.zsh を実行してから、パイプラインを開始します。ジョブが終了したら計測を停止してください。RSSの単位は通常KBです。プロセスの仮想アドレス空間全体を表す値ではありませんが、メモリ使用量が増え続けるコンパイラ、シミュレータ、テストプロセスを特定するには十分です。
ステージマーカーも記録する
依存関係の解決、コンパイル、リンク、シミュレータの起動、テストの実行について、それぞれ開始前と終了後にUTC時刻を出力します。これにより、スワップ使用量が急増した時点を具体的な処理段階に対応付けられます。パイプラインの文脈がないシステムスナップショットだけが残る事態を避けられます。
メモリ圧迫の原因を分類する
よくあるパターンは、次の表を使って大まかに分類できます。
| 現象 | 可能性が高い原因 | 次の対応 |
|---|---|---|
| コンパイル中に複数プロセスのRSSが同時に増える | コンパイルの並列数がメモリ予算を超えている | ビルドジョブ数を減らして再計測する |
| シミュレータ起動後に圧迫が急増する | 並列実行するテスト先が多すぎる | 同時に実行するシミュレータ数を制限する |
| 単一のテストプロセスでRSSが増え続ける | テストまたはテスト対象コードでのリーク | テストを分割してプロセスサンプルを取得する |
| swapが増え続け、所要時間のばらつきも拡大する | システムでページングが頻発している | 並列数を減らし、常駐する不要なバックグラウンドタスクを削減する |
| 圧迫は正常だがジョブが終了させられる | メモリ以外の問題である可能性がある | 終了コードと統合ログを確認する |
異常終了の直後に、関連するシステムログを検索できます。
log show --last 30m --style compact \
| grep -Ei 'memory pressure|memorystatus|killed process' \
> memory-events.log
一致するログがないからといって、メモリの状態が正常だったとは断定できません。計測値の推移とパイプラインの終了コードを併せて確認する必要があります。また、単一プロセスのピーク値だけでリークと判断しないでください。コンパイルやリンクでは短時間のピークが本来発生します。処理段階が終了しても増加が続き、値が下がらない場合のほうが疑わしい挙動です。
並列数を明確な予算として設定する
並列数の上限は、計測したピーク値に基づいて決めます。まず物理メモリの約20%をシステム、リモートセッション、ログプロセス用に確保し、残りの容量にコンパイラとシミュレータを収めます。コンパイルとテストが同時に実行されない場合は、予算を個別に設定できます。パイプライン内で両者が重なる場合は、必要量を合算しなければなりません。
Xcodeのビルドでは、まず -jobs を使って保守的な設定を試します。
set -o pipefail
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-jobs 4 \
build
シミュレータテストでは、並列実行するテスト先の数を制限します。
xcodebuild \
-workspace App.xcworkspace \
-scheme AppTests \
-parallel-testing-enabled YES \
-maximum-concurrent-test-simulator-destinations 2 \
test
例にある4と2をそのまま使用しないでください。まず現在の並列数を半分にし、同じジョブを少なくとも3回連続で実行してから、段階的に増やします。目標はCPUを常に最大稼働させることではありません。スワップ使用量が増え続けない範囲で、安定したスループットを得ることです。
受け入れチェックリストで調整を完了する
各ラウンドではパラメータを1つだけ変更し、次の結果を記録します。
- 同じコミットが3回連続で完了したか。
- スワップ使用量のピーク値と、ジョブ開始前から終了後までの増分。
- メモリ圧迫が高まったパイプラインの段階。
- プロセスの異常終了が引き続き発生しているか。
- 合計所要時間、中央値、3回の結果のばらつき。
- 並列数を減らしたことで、単位時間あたりに完了する有効なジョブ数が改善したか。
並列数を減らすことで1回のビルドがわずかに遅くなっても、再実行やランダムなプロセス終了がなくなれば、全体のスループットは通常むしろ向上します。設定を確認したら、並列数をパイプライン構成に記述します。計測スクリプトは常時高頻度で動かすのではなく、障害発生時に有効化する診断手段として残してください。構成を変更した場合やテストマトリクスを拡大した場合は、再度計測します。コンソールで現在選択できる構成を確認した後も、同じベンチマークジョブを使って新たに予算を設定してください。
よくある質問
クラウドMacのメモリ圧迫は空きメモリだけで判断できますか?
判断できません。memory_pressure、圧縮メモリ、スワップ増加量、プロセスRSSを併せて確認します。空き容量が少なくても、継続的なスワップがなければ直ちに異常とは限りません。
Xcodeの並列数を減らした効果はどう検証しますか?
同じコミット、依存キャッシュ、テスト集合で最低3回実行し、最大スワップ量、異常終了、総時間、ばらつきを比較します。失敗と変動が同時に減ることを確認します。
専有物理クラウドMac
再現可能な開発環境を専有物理ノードにデプロイ
構成、利用期間、5つのノードのいずれかを選択し、Xcodeビルド、自動テスト、リモート開発、モデル推論に利用できます。