工程文章

云端 Mac 持续构建热降频诊断实战

云端 Mac 持续构建热降频诊断实战

同一提交在云端 Mac 上连续构建时,第一轮 11 分钟、第四轮却变成 15 分钟,先别把结论写成“机器性能不稳定”。缓存命中、索引进程、测试并发和热压力都可能形成相似曲线。正确做法是固定变量、连续采样,再判断耗时增长是否与功耗和热压力同步。

先定义可比较的构建样本

诊断对象应是能稳定复现的命令,而不是开发者临时点击的 Scheme。固定代码提交、Xcode 路径、SDK、目标、构建配置和 DerivedData 目录,并记录机器启动后的空闲时间。若每轮都切换分支或更新依赖,测到的只是工作负载变化。

在 VMOrbit 的独享物理节点上,宿主资源不会被其他租户的虚拟机调度干扰,但节点内仍可能存在索引、依赖解析、模拟器和遗留构建进程之间的竞争。开始前先保存基本环境:

mkdir -p "$HOME/build-thermal-check"
system_profiler SPHardwareDataType > "$HOME/build-thermal-check/hardware.txt"
xcodebuild -version > "$HOME/build-thermal-check/xcode.txt"
pmset -g > "$HOME/build-thermal-check/power.txt"
ps -axo pid,%cpu,%mem,etime,command > "$HOME/build-thermal-check/processes-before.txt"

冷构建与热构建分开测

冷构建用于观察完整编译负载,热构建用于观察增量路径。两者不能混在同一组统计里。冷构建应使用新的专用 DerivedData,热构建则复用前一轮目录;不要清理全局缓存,以免影响节点上的其他任务。

样本 DerivedData 主要用途
冷构建 每组重新创建 比较完整编译的持续负载
热构建 同组复用 检查增量构建和缓存效果
空载对照 不执行构建 识别后台进程与基线功耗

同时采集耗时与热压力

只看总耗时无法证明热降频。至少连续运行三轮,并在构建期间同步采集 powermetrics。该工具通常需要管理员权限,应将授权限制在明确命令和参数上。

RUN_ROOT="$HOME/build-thermal-check/run-01"
mkdir -p "$RUN_ROOT/DerivedData"

sudo powermetrics --samplers cpu_power -i 1000 -n 900 \
  -o "$RUN_ROOT/powermetrics.txt" &
METRIC_PID=$!

/usr/bin/time -lp xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  -derivedDataPath "$RUN_ROOT/DerivedData" \
  build > "$RUN_ROOT/build.log" 2> "$RUN_ROOT/time.txt"

wait "$METRIC_PID"
pmset -g therm > "$RUN_ROOT/thermal.txt"

-n 900 代表最多采集 900 次;若构建更短,后台采样仍会继续。工程脚本可在构建结束后向采样进程发送 TERM,但要保留退出处理,避免 CI 留下采样进程。无法使用 powermetrics 时,至少保存 pmset -g thermtime -lp 和按 CPU 排序的进程快照。

热降频是“耗时趋势”和“系统信号”共同支持的结论,不是看到一次高 CPU 占用就能确认的标签。

用曲线而不是单点下结论

把每轮的实际耗时、最大常驻内存、热压力和同期高负载进程放到同一张记录表。若第一轮慢、后续明显变快,通常是缓存预热;若某一轮突然变慢,同时出现依赖下载或索引进程,更可能是资源竞争。

更值得关注的模式是:代码和缓存状态不变,连续几轮耗时逐步增长;CPU 长时间满载;热压力信号随之升高;停止负载并静置后,下一轮恢复。只有这些证据同时出现,才应把热降频列为主要原因。

排除三类常见假象

首先检查构建是否偷偷执行了测试、归档或脚本下载。其次比较日志中的编译文件数量,确认每轮工作量相同。最后核对是否存在多个 xcodebuild、模拟器或包管理进程并行运行。可以在异常轮次保存:

date -u
pgrep -alf 'xcodebuild|swift-frontend|clang|Simulator'
top -l 3 -o cpu -stats pid,command,cpu,mem,time
du -sh "$RUN_ROOT/DerivedData"

top 的瞬时排名只能说明采样时谁在消耗资源,不能单独证明根因。构建日志中的任务数量和持续时间才是连接系统状态与工程负载的依据。

通过对照实验定位触发条件

确认趋势后,一次只改一个变量。先把 CI 并发作业数降为 1,保持其他条件不变;再恢复并发,改为错峰启动;最后比较冷构建与热构建。若降低并发后耗时曲线恢复平稳,问题更可能是同节点任务竞争或持续功耗过高,而不是编译器本身退化。

不要直接用更激进的清缓存作为修复。清理会让下一轮工作量增大,并掩盖原有趋势。也不要用重启作为唯一证据:重启同时终止后台进程、清空部分状态并带来冷却时间,无法说明究竟是哪项变化起效。

调整构建编排而非追逐瞬时频率

稳定方案通常是限制同一节点上的重型任务数,把归档、全量测试和静态分析错开,并给每个流水线分配独立 DerivedData。对于必须并行的任务,可先测出并发 1、2、3 时的中位耗时,再选择总吞吐量最高而非单任务峰值最高的档位。

把诊断结果固化为 CI 基线

基线应保存命令、提交、Xcode 版本、构建类型、连续运行次数和中位耗时。不要用最快一轮当基准,也不要因一次超时就阻断合并。更稳妥的规则是滚动比较最近若干次中位数,连续多次超过阈值后再生成诊断记录。

最终产物至少包括构建日志、time 输出、热压力记录、进程快照和采样参数。若问题需要升级处理,附上节点区域、发生时间、复现步骤和最小必要日志,但不要提交密码、私钥或完整访问凭据。这样下一次出现相似波动时,团队可以直接复跑同一套样本,而不是重新猜测。

常见问题

单次 Xcode 构建变慢能否直接判断为热降频?

不能。至少应在固定代码、Xcode 版本、并发度和缓存状态后连续运行三次,并同时查看 CPU 功耗、频率、热压力与后台进程;只有耗时持续上升且热压力信号同步变化时,才应优先怀疑热降频。

powermetrics 必须以管理员权限运行吗?

通常需要。可用 sudo 仅执行限定参数的采样命令,并把输出写入受控目录;若无法授权,则使用 pmset、time、top 和构建日志组合判断,但结论精度会降低。

独享物理节点

把构建任务放到独享云端 Mac

选择 VMOrbit M4 或 VMOrbit M4 Pro,并按团队位置选择节点。实际可用性与交付信息以控制台实时返回为准。

选择云端 Mac