- 187 次 PR 裡,約 86.6% 是 warm——日常 SLA 該盯 warm,別把 cold 混進 P95
- 改依賴、清快取、換 scheme 會觸發「冷啟動」,牆鐘差 2–3 倍 很常見
- 就算全是 warm,快取 key 沒設好、多個 job 搶磁碟、測試和簽章塞在同一個 job,還是會忽快忽慢
- 大致順序:排隊 → 分清 cold/warm → 快取 → 控並發 → 最後才談換機器(對照見 瀑布拆解)
完整對照數據 → GitHub Actions 優化主軸 · Benchmark
1. CI 像抽獎:同 commit,差好幾倍
在 GitHub Actions 上跑 iOS CI,下面這些畫面應該不陌生:
- 同一個 commit 重跑一遍,牆鐘能差 2–3 倍
- 儀表板 P95 紅得嚇人,團隊體感卻是「平常 merge 也沒等那麼久」
pod install有時半分鐘就結束,有時像卡住了- 週五下午不敢點 merge——怕 CI 又「抽中」慢的那檔
未必是機器算力不夠。我們跟團隊做過 14 天 Shadow 雙軌對照,更常見的真相是:數字算混了,環境也不穩——快取留不住、磁碟被搶,體感就像抽獎。換晶片往往排在很後面。
這篇只聊一件事:構建為什麼忽快忽慢。job 卡在佇列裡等 runner?看 排隊怎麼破。快取怎麼設、買機租機划不划算,留給 快取專題 和 ROI 那篇。
2. cold 和 warm:別混在一起算
有個坑特別常見:把每次構建的耗時全倒進一個 P95。偶爾來一趟「冷啟動」,尾巴就被拉很長——看數據覺得「天天都很慢」,其實日常 merge 沒那回事。
- warm:依賴沒動、快取還在、scheme 沒換——多半是增量編譯,代表「日常 merge 長什麼樣」
- cold:鎖檔變了、快取清了、換 target 或 runner 上第一次跑——得重新 resolve、裝 Pod、大面積重編
日常 SLA 盯 warm 的 P50/P95 就夠了;cold 另畫一條線,別和 merge 體驗綁在一起。
2.1 什麼情況會「冷啟動」
在 macos-latest 上 cold 更常見——job 跑完 workspace 往往就清掉了,快取很難「住下來」:
| 觸發條件 | 典型耗時增量 | 日誌裡常見關鍵字 |
|---|---|---|
Podfile.lock 變更 |
+3–8 分鐘 | pod install、Downloading dependencies |
| DerivedData 未命中 / 被清 | +5–15 分鐘 | CompileSwift、全量 .o 重建 |
| 切換 scheme / 新 target | +2–10 分鐘 | 不同 xcodebuild -scheme |
| SPM 依賴解析變更 | +1–5 分鐘 | Resolve Package Graph |
| Xcode 小版本被動升級(macos-latest) | 首次構建 +10–20 分鐘 | 新 SDK / 模組快取重建 |
依賴升得勤,cold 自然多——不是機器退化,是這週幹的活不一樣。報告時把「升 Pod 那週」和「平常開發週」分開,數字才說得通。
2.2 warm 為什麼還是不穩
就算全是 warm,牆鐘仍可能上下飄個三成左右,常見是這些:
- 快取沒命中:key 少寫了
arm64、沒綁分支,或多個 job 搶同一個槽 - 同一 org 裡 macOS job 扎堆,磁碟和網路互相拖
- 這次只編 main,下次全量 unit + UI test——工作量本就不一樣
- 改動面不同:動 Pod 原始碼和改個 SwiftUI 預覽,compile 量差很遠
3. 時間到底耗在哪
給 workflow 各步打個時間戳,牆鐘大致能拆成五塊。下面是 warm 時比較常見的分布(專案不同會有出入):
① checkout + env setup ~0:30 – 1:30 ② pod install / SPM resolve ~0:30 – 2:00 (cold ↑↑) ③ xcodebuild compile+link ~3:00 – 8:00 (change surface) ④ tests (simulator / unit) ~1:00 – 6:00 (optional, often underestimated) ⑤ archive + codesign ~1:00 – 4:00 (release pipeline) Typical warm P50 range: 6 – 14 minutes
實用做法:用 step 耗時或 time 分別看 pod install、xcodebuild build、xcodebuild test。② 經常五分鐘往上——先想 cold 和快取;③ 忽長忽短——查 DerivedData 和並發;④ 總是拖後腿——測試拆出去,或放 nightly 全量。
4. 測試和簽章:隱形拖後腿
不少「xcodebuild 好慢」的回饋,拆開看是測試或簽章被算進同一次 build:
- Simulator 冷啟動:CI 上第一次拉起可能要等多幾分鐘,沒預熱就每個 job 重來一遍
- UI Test 比 unit 慢一個量級,和 compile 塞一個 job 裡,P95 很難看
- 憑證、Keychain、Profile 下載——託管 runner 上常常每次 job 重新設一遍
- Archive、打 IPA 是發版流水線的事,別和 PR 驗證的耗時攪在一起
PR:build + 輕量測試,盯 warm P95。
TestFlight / 發版:單獨 workflow、單獨指標。混在一處,「平常 merge 要等多久」永遠對不上號。
5. 14 天 Shadow 實測
下面是一組雙軌對照:macos-latest 和獨享 Mac mini M4 各跑 187 次 PR,Xcode 16.2、CocoaPods 1.15.2 對齊。完整做法見 主軸 Benchmark。
| 分類 | 樣本數 | 占比 | macos-latest P95 | 獨享 M4 P95 |
|---|---|---|---|---|
| warm build | 162 | 86.6% | 14:12 | 6:05 |
| cold build | 25 | 13.4% | 19:40 | 11:20 |
| 混在一起算(容易誤判) | 187 | 100% | ~16:00+ | ~7:30+ |
如果把 cold 和 warm 攪在一起報 P95,日常體驗會被高估大概 15–25%,容易得出「得馬上換機器」的結論。更穩的做法:merge 體驗看 warm,升依賴那幾週單獨預期 cold。
獨享 M4 上 warm 仍比 cold 快一截,說明讓 DerivedData、Pods 有固定「家」很值——快取怎麼設,我們另文細說(見 快取專題)。
6. 怎麼排:從看清到快取
排隊問題排除(或已經不大)之後,可以按這個順序來,別一上來就談買機器:
- 給構建打標籤:cold 還是 warm(Podfile.lock 變沒變、快取中沒中)
- 日常 SLA 只盯 warm P95;cold 週報或單獨一條線
- 把 DerivedData、Pods、SPM 的快取策略定下來 — 快取最佳實踐(籌備中)
- macOS job 別擠太滿:self-hosted 一台 1–2 個並發就夠;託管側也盡量別多 job 共用 workspace
- PR 驗證和發版/signing 拆開,別讓測試污染 merge 指標
- 快取都到位了還不達標,再聊 M4 / M4 Pro — 見 晶片與工時
- name: Classify build type
run: |
if git diff --name-only HEAD~1 | grep -q Podfile.lock; then
echo "BUILD_TYPE=cold" >> $GITHUB_ENV
else
echo "BUILD_TYPE=warm" >> $GITHUB_ENV
fi
- name: Record wall clock
run: |
echo "build_type=${BUILD_TYPE}" >> metrics.csv
echo "wall_sec=$(( $(date +%s) - START ))" >> metrics.csv
7. 常見問題(FAQ)
同 commit 重跑差 2–3 倍,正常嗎?
在 macos-latest 上挺常見:一回快取全中,一回 miss 疊上排隊,就像兩檔不同的 build。先看兩次 pod install 和 cache hit 日誌,別急著換晶片。
P95 到底怎麼算才對?
日常 merge 的體感,用 warm 算 P95 就行,兩週攢夠三十次樣本比較穩。cold 另算或只記次數——混在一起,採購決策容易偏。
pod install 每次都很慢,是 CocoaPods 的鍋嗎?
託管 runner 上更常是 Pods 沒地方「住」、cache key 沒綁分支和架構。self-hosted 指到固定大碟,warm 時半分鐘以內很常見。
換 Mac mini M4 能消除波動嗎?
對照裡 warm P95 能降一大截(−57%),波動也收斂(σ −40%),但分不清 cold/warm、快取沒設計好,獨享機照樣會有尖刺。
慢是排隊還是 build 本身?
看日誌開頭 Waiting for a runner 等了多久。排隊算牆鐘、不算 GitHub 分鐘費。queue 幾乎為零還慢,就是這篇聊的構建側 → 接著看快取。
還想往下讀?
快取 YAML 和 key 怎麼設計 → 快取專題;買還是租、划不划算 → ROI 模型 和 月構建 500 次怎麼選。
8. 做個總結
GitHub Actions 上 iOS CI「慢」,很少是單純 CPU 不夠。更常是這幾件事疊在一起:
- cold 和 warm 混在一起看,P95 被抬高了
- 託管 job 跑完就清碟,快取留不住
- 測試、簽章和 PR build 攪在一個 job 裡
先把 warm 的 P95 算清楚,再快取 → 並發 → 硬體。14 天 Shadow 裡,只做對指標和快取,warm P95 就能從 14:12 拉到 6:05——不必先拍板買機器。
想讓 DerivedData 有個固定「家」?
Vuncloud Cloud Mac M4 Pro 帶 1TB 資料碟、預裝 actions-runner,DerivedData / Pods 可以一直留在碟上,託管 runner 那種「每次冷啟動」會少很多。