同一台云端 Mac 同时执行 Xcode 构建、测试、日志压缩和产物校验时,最常见的问题不是某个进程彻底失控,而是多个“都能晚一点完成”的辅助任务与关键构建争抢 CPU、磁盘和内存带宽。结果往往表现为编译耗时波动、测试超时或远程桌面响应迟缓。解决这类问题,第一步不是盲目降低所有进程优先级,而是先区分关键路径与后台路径。
先建立可比较的进程基线
在没有其他任务时执行一次代表性构建,再在压缩、校验和索引同时运行时重复执行。两次都记录构建耗时、失败阶段以及主要进程的 CPU、内存、状态和 nice 值。
mkdir -p "$HOME/ci-observe"
ps -axo pid,ppid,user,%cpu,%mem,state,nice,etime,command \
| sort -k4 -nr \
| head -n 40 \
> "$HOME/ci-observe/processes.txt"
iostat -w 2 -c 15 > "$HOME/ci-observe/iostat.txt"
vm_stat 2 15 > "$HOME/ci-observe/vm-stat.txt"
采样要覆盖实际工作区和真实依赖规模,但不要把首次下载依赖与稳定构建混在同一基线里。若构建时间变长,同时 iostat 持续繁忙,而压缩进程占据明显 CPU,就有理由调整辅助任务;若主要问题是内存压力或测试并发过高,仅改优先级通常无效。
优先级是一种调度提示,不是资源上限。它适合解决“谁先运行”,不能替代并发控制,也不能保证某个任务获得固定比例的 CPU。
按关键路径给任务分级
可以把 CI 进程分成三类。第一类是编译、链接、测试和产物导出,它们决定流水线是否完成,应保持正常优先级。第二类是日志压缩、校验和生成、旧产物整理,这些任务必须完成,但通常不必抢在构建之前。第三类是编辑器索引、交互式分析和临时诊断,只应在需要时启动。
不要按工具名称一刀切
同一个工具可能处于不同路径。例如 tar 在构建前解包必需依赖时属于关键任务,在构建后压缩日志时属于后台任务。判断标准应是:它是否阻塞下一步、失败是否必须终止流水线、输出是否被当前任务立即使用。
建议把分级写入流水线配置,而不是依靠操作者临时执行。这样脚本审查时可以直接看到哪些命令被降级,也能避免新任务默认继承不合适的策略。
用 nice 与 taskpolicy 包装后台任务
nice 调整进程的调度优先值,正数表示更愿意让出执行机会。普通用户可以降低自己启动进程的优先级,但不能随意把进程提升到更高优先级。macOS 的 taskpolicy -b 可把命令放入后台策略,影响范围不只限于 CPU,因此应只用于已经确认不阻塞关键路径的任务。
下面的脚本让校验和在后台低优先级运行,同时让 Xcode 构建保持正常策略,并分别保留退出状态:
#!/bin/zsh
set -u
mkdir -p Artifacts Logs
taskpolicy -b nice -n 10 /bin/sh -c \
'find Artifacts -type f -print0 | xargs -0 shasum -a 256 > Logs/checksums.txt' &
audit_pid=$!
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-destination 'generic/platform=iOS' \
build > Logs/xcodebuild.log 2>&1
build_rc=$?
wait "$audit_pid"
audit_rc=$?
if (( build_rc != 0 )); then
exit "$build_rc"
fi
exit "$audit_rc"
不要给整个 CI Runner 统一套上 taskpolicy -b。这样会连编译器、测试进程和子进程一起降级,表现为整条流水线变慢,却看不出具体责任任务。包装应尽量靠近单条后台命令。
在应用代码中正确使用 QoS
如果辅助工作由自研 Swift 工具执行,应在代码层使用队列 QoS,而不是启动后再猜测进程职责。用户发起并等待结果的工作可使用 .userInitiated;日志整理、缓存清点等非即时工作可使用 .utility。.background 只适合用户不等待、延后完成也不会影响当前结果的任务。
let queue = DispatchQueue(
label: "com.surevm.ci.artifact-audit",
qos: .utility
)
queue.async {
auditArtifacts()
}
不要为了“更快”把所有队列设为 .userInteractive。过高的 QoS 会让辅助任务与关键工作争抢调度资源,还可能造成优先级反转:高优先级任务等待一个被低优先级队列持有的锁。共享锁、串行队列和同步调用都应纳入检查。
验证效果并设置回退条件
调整后至少重复三轮同样的构建输入,比较中位耗时、失败位置和资源采样。单次变快不能证明策略有效。还要检查日志压缩、校验和等后台任务是否完整结束,不能用“关键构建成功”掩盖辅助任务失败。
验收时可依次确认:
- 编译、链接和测试进程没有继承后台策略。
- 后台任务失败会返回非零退出码。
- Runner 同时执行的流水线数量未超过节点承受范围。
- 调整前后使用相同代码、依赖锁文件和 Xcode 版本。
- 远程桌面操作不是唯一判断依据,必须保留进程与 I/O 采样。
- 节点重启后,优先级包装仍由流水线脚本自动生效。
如果降级后辅助任务积压到下一轮构建,说明问题已从资源争抢变成吞吐不足。此时应减少后台任务数量、缩小处理范围或将其移到构建完成后的独立阶段,而不是继续提高 nice 值。SureVM 节点上的具体可选配置应在控制台确认,但无论使用哪档配置,先分清关键路径、再改变调度策略,通常比全局调整更容易验证和回退。
常见问题
nice 和 taskpolicy 能限制进程最多使用多少 CPU 吗?
不能。它们调整的是调度倾向和后台策略,不是硬性资源配额。需要限制并发时,应同时控制构建任务数、测试分片数和后台作业数量。
哪些 CI 任务适合放入后台策略?
日志压缩、校验和生成、旧产物整理和非阻塞索引通常适合降级;编译、链接、测试执行及其直接依赖不应默认放入后台策略。
为构建、开发与实验选择独享物理 Mac 节点
比较两档 Mac mini M4 配置、四种租期和五个可订购节点,再按实际工作流完成选择。