同一條 CI 流水線有時 18 分鐘就能完成,有時卻停滯在測試階段,重新執行後又恢復正常。遇到這種情況,先別歸因於「機器偶爾變慢」。在雲端 Mac 上,編譯器、連結器、模擬器與測試程序會同時爭用統一記憶體。只看建置結果,無法判斷問題究竟來自程式碼、過高的並行度,還是系統已進入持續壓縮與分頁交換狀態。
先定義可重現的觀察時段
診斷時必須固定提交版本、相依套件快取狀態、建置命令與測試集合。選擇一項能穩定觸發問題的作業,記錄開始時間、結束時間、結束碼與失敗階段。第一次採樣時,不要一邊清除快取、一邊調整並行度,否則無法判斷究竟是哪個變數產生效果。
先確認實體記憶體與交換空間的基準值:
sysctl -n hw.memsize
sysctl vm.swapusage
memory_pressure -Q
vm_stat
hw.memsize 是實體記憶體的位元組數。vm.swapusage 顯示目前的交換空間用量;vm_stat 中的頁面大小、壓縮頁面及換入換出計數,可用來判斷變化趨勢。重點是作業前後的增量,不要把某一刻的數值直接當成門檻。
可用記憶體偏低不代表記憶體不足。macOS 會主動使用記憶體快取檔案頁面;真正需要關注的是記憶體壓力持續升高、交換空間連續增加,且建置時間延長或程序結束同時發生。
在建置期間持續採樣
將診斷命令放在建置命令之外執行,避免某個子程序結束後,相關證據也隨之消失。以下指令碼每 10 秒儲存一次記憶體壓力、交換空間,以及 RSS 排名前 15 的程序:
#!/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。先將目前的並行度減半,連續執行相同作業至少三次,再逐級提高。目標不是讓 CPU 每秒都處於滿載狀態,而是在交換空間不持續增加的前提下,獲得穩定的吞吐量。
使用驗收表完成調校
每一輪只修改一個參數,並記錄以下結果:
- 同一提交版本是否能連續完成三次。
- 交換空間峰值,以及作業前後的增量。
- 記憶體壓力在哪個流水線階段開始升高。
- 是否仍有程序異常結束。
- 總執行時間、中位執行時間,以及三次結果的離散程度。
- 降低並行度後,單位時間內完成的有效作業數是否有所改善。
如果較低的並行度讓單次建置稍微變慢,卻能消除重新執行與隨機結束,整體吞吐量通常反而更高。確認參數後,將並行值寫入流水線設定,並保留採樣指令碼作為故障排查開關,而不是讓它永久以高頻率執行。更換配置或擴大測試矩陣時,應重新量測;在控制台確認目前可選配置後,也應使用同一組基準作業重新建立預算。
常見問題
雲端 Mac 發生記憶體壓力時,只看可用記憶體足夠嗎?
不足夠。應同時查看 memory_pressure、壓縮記憶體、交換空間增量與程序 RSS;可用記憶體偏少不一定異常,持續換頁並伴隨建置變慢才需處理。
降低 Xcode 並行數後,如何確認調整有效?
以相同提交、依賴快取及測試集合至少執行三次,比較交換空間峰值、異常退出、總耗時與波動;失敗率和變異同時下降才代表調整有效。
獨享實體雲端 Mac
將可重現的開發環境部署至獨享實體節點
選擇設定、租期與五個節點之一,用於 Xcode 建置、自動化測試、遠端開發或模型推論。