當一個 Swift 模組的編譯時間從數十秒逐漸拉長到數分鐘時,不要急著重寫程式碼或提高平行度。在遠端環境中,依賴解析、建置腳本、連結與型別檢查都會計入 Xcode 顯示的總耗時。有效的排查方式,是先固定雲端 Mac 的工具鏈與輸入,取得各階段的量測證據,再將問題縮小到具體的原始碼檔案與運算式。
先建立可重現的量測基準
選擇沒有其他建置工作執行的時段,固定程式碼提交、Scheme、Configuration、SDK 與目標架構。記錄 xcodebuild -version、目前提交,以及實際執行的命令。不要直接拿一次乾淨建置和一次增量建置比較,因為兩者回答的是不同問題。
mkdir -p "$HOME/build-audit"
xcodebuild -version > "$HOME/build-audit/toolchain.txt"
git rev-parse HEAD > "$HOME/build-audit/commit.txt"
set -o pipefail
/usr/bin/time -l xcodebuild \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-configuration Release \
-destination 'generic/platform=iOS' \
-showBuildTimingSummary \
build \
2>&1 | tee "$HOME/build-audit/baseline.log"
先連續執行三次一般的增量建置,確認耗時波動是否穩定。需要量測乾淨建置時,應明確執行 clean,或使用獨立的 DerivedData 路徑,並確保最佳化前後採用完全相同的清理方式。
單次最快結果不能作為結論。應比較相同條件下多次執行的中位數,同時保留最慢一次的記錄,用來檢查偶發執行的腳本、網路請求或資源競爭。
從階段摘要判斷調查方向
Build Timing Summary 會列出編譯、連結、資源處理與腳本等階段。應先找出占比最高且反覆出現的項目,不要只看最後一行的總耗時。
| 現象 | 優先檢查 | 常見誤判 |
|---|---|---|
| SwiftCompile 長時間占首位 | 型別檢查、單一檔案大小、批次編譯行為 | 誤以為連結速度慢 |
| Run Script 每次都執行 | 輸入與輸出檔案、腳本內部的掃描範圍 | 只提高機器的平行度 |
| 依賴解析耗時不穩定 | 鎖定檔、儲存庫存取、是否重複解析 | 當成 Swift 編譯問題 |
| Link 階段特別突出 | 連結輸入、除錯符號、重複程式庫 | 重寫業務邏輯運算式 |
也要檢查腳本是否宣告輸入與輸出。缺少依賴邊界的腳本,可能在每次增量建置時執行,掩蓋原始碼最佳化帶來的效果。若腳本包含下載操作,應分別量測網路等待與本機運算時間。
定位緩慢的函式與運算式
確認瓶頸位於 Swift 編譯後,再暫時加入前端診斷參數。可以在專用診斷 Configuration 的 OTHER_SWIFT_FLAGS 中設定,避免影響團隊日常使用的設定。
-Xfrontend -debug-time-function-bodies
-Xfrontend -debug-time-expression-type-checking
重新執行同一條建置命令,並一併儲存標準錯誤輸出。記錄通常會提供耗時、檔案位置,以及對應的函式或運算式。不同 Xcode 工具鏈的輸出格式與門檻可能改變,因此解析腳本不應假設欄位數量永遠固定。
應優先處理哪些項目
先依耗時排序,再按檔案彙整。一個會重複編譯數百次的中度熱點,可能比單一極慢函式更值得優先處理。常見的高成本程式碼包括過長的泛型鏈、巢狀閉包、包含大量分支的單一運算式,以及迫使編譯器同時推斷多個中間型別的集合轉換。
修改時,每次只處理一類問題,例如拆分運算式、為中間值補上明確型別,或將大型函式拆成邊界清楚的小函式。不要同時變更編譯設定與原始碼,否則無法判斷改善究竟來自哪裡。
用最小變更驗證原因
假設某段程式碼將篩選、映射、字典建立與可選值處理串在同一個運算式中,可以先為中間結果命名並標註型別。目的不是讓原始碼更短,而是減少型別檢查器一次必須求解的約束。
let validItems: [Item] = items.filter { $0.isValid }
let identifiers: [String] = validItems.map(\.identifier)
let result: [String: Item] = Dictionary(
uniqueKeysWithValues: zip(identifiers, validItems)
)
完成修改後,使用一個相較於基準提交只包含該項變更的分支重新量測。至少核對三點:熱點記錄中該運算式的耗時是否下降、SwiftCompile 階段是否縮短,以及總建置時間是否在多次執行中穩定改善。若只有總時間改變而熱點沒有變化,應繼續檢查快取或背景工作,不要將結果歸因於原始碼調整。
將診斷納入可維護的驗收流程
診斷參數不適合長期加入所有建置。較穩妥的做法,是建立獨立的效能檢查工作,按需執行,並封存工具鏈、提交、命令與摘要。記錄可能包含本機路徑、儲存庫結構,或環境變數展開後的內容,上傳前應先去除敏感資訊。
建議每次調查都保存以下內容:
- Xcode 與 Swift 版本。
- Git 提交與建置設定。
- 清楚標示乾淨建置或增量建置。
- 三次以上執行的原始耗時。
- 階段摘要與主要原始碼熱點。
- 修改前後的唯一差異與回復方式。
如果結果在雲端 Mac 上波動明顯,應先確認是否有平行建置、索引工作或殘留腳本正在執行,再重新量測。MiniRent 提供的是獨享實體節點,但同一台裝置內由使用者啟動的工作,仍會競爭 CPU、記憶體與磁碟資源。可靠的結論來自受控的輸入、可重現的命令與完整保留的證據,而不是一次看起來比較快的建置。
常見問題
為什麼不能只比較兩次 Xcode 的總建置時間?
總時間同時包含相依套件解析、快取狀態、腳本與連結。應先固定提交、Xcode 版本和參數,再比較各階段及 Swift 原始碼熱點。
Swift 編譯時間診斷參數適合長期保留嗎?
不適合。這些參數會產生大量記錄,行為也可能隨工具鏈改變。完成取證後應移除,只在效能回歸調查時按需啟用。
專用雲端 Mac
在獨享實體節點上執行建置任務
按天、週、月或季選擇 MiniRents M4 與 MiniRents M4 Pro,並依團隊與程式碼儲存庫位置選擇節點。設備為獨享實體機,並非虛擬機。