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 构建、自动化测试、远程开发或模型推理。