移动团队总希望每台 Mac mini M4 都跑「所有模拟器」,但过载的第一信号往往不是 CPU,而是压缩内存、SpringBoard 启动卡住,以及把并行度减一就消失的 XCTest 超时。本文给出两张矩阵(内存档位与安全并行路数、节流策略与清理节奏),八条可直接贴进变更单的上线步骤,以及 FAQ 结构化数据,方便值班同学引用而不用翻 Slack。下文同时强调如何把观测写进周会:用「每小时模拟器启动次数」对比「合并请求吞吐」,比口头争论「是不是 Xcode 又坏了」更有效。
信号:你在并行模拟器而不是缩短队列
- 夜间 flaky 成簇:夜间流水线把四套 UI 套件叠在同一台主机,而笔记本开两个模拟器却全绿。
- SpringBoard 或 backboardd 看门狗杀进程:即便
xcodebuild间歇仍报成功。 - 磁盘在
~/Library/Developer/CoreSimulator下疯长,CPU 却低于六成——内存压力触发分页与镜像抖动。
若你已在多机之间分片测试包,请先读 Mac mini M4 上的 UI 测试分片 了解如何水平切分;本文管的是单机纵向天花板:物理机上最多能同时承载多少模拟器而不让分页统治墙钟时间。把「分片」与「单机路数」混为一谈,是多数团队反复踩坑的根因。
另一个常见误区是把「Runner 队列变短」等同于「提高并行工作者」。若 p95 作业时长在加并行后反而上升,通常说明瓶颈在内存与 I/O,而不是 CPU。此时应回到矩阵 A 对应行,先收紧并行,再评估是否增购 UI 专用主机;否则只是在用随机超时支付技术债。把 CoreSimulator 目录大小与压缩内存曲线并排展示,比只看 Jenkins 绿灯更能说服管理层。
矩阵 A — 统一内存档位与安全并行模拟器路数
假设 Xcode 16 时代运行时、Apple Silicon M4,且 UI 测试会截屏。若同一台机还承担编译且未拆编译池,请在本表基础上再降一档。
| 内存 | 并行 UI 模拟器(同一大版本系统) | 仅单测的并行模拟器 | 硬停症状 |
|---|---|---|---|
| 16 GB | 1(极小套件可尝试 2) | 严格 teardown 下最多 3 | SpringBoard 重启循环 > 2 次/小时 |
| 24 GB | 2 路 UI,限制截图 | 挂空闲关机钩子时可到 4 | 压缩内存持续 > 5 GB |
| 32 GB+ | 编译隔离时 3 路 UI | 每作业 RSS 预算到位时最多 6 | 套件运行中 APFS 剩余 < 45 GB |
运维落地时,建议把「UI 路数」写成编排器标签的硬约束,而不是文档里的软提示。对混合租户池,默认宁可排队也不要在 16 GB 上开第二条 UI 路;对单团队独占机,可在变更窗口内试验加一路,但必须同时打开磁盘与压缩内存告警,否则无法判断是「真有余量」还是「运气好没撞上大套件」。
矩阵 B — 节流策略与清理节奏(✓ / ✗)
| 策略 | 利于内存 | 过度则伤吞吐 | 何时采用 |
|---|---|---|---|
| 每作业后关闭模拟器 | ✓ | ✓ | 无独立编译主机的混合租户池 |
| 整条流水线复用已启动模拟器 | ✗ | ✗ | 单团队独占机且有每日清理窗口 |
| 每周抹除不可用设备 | ✓ | ✓ | 始终;并与 磁盘留存 指标联动 |
为何分页躲在 CoreSimulator 里而不是进程列表顶端
活动监视器里看似许多进程 footprint 不大,但模拟器栈在图形与文件系统上会分配大块后备存储,应用安装阶段尤其尖峰。Apple Silicon 统一内存意味着这些尖峰会与编译守护进程、Swift 运行时缓存争抢。实务上不是把 xcodebuild -parallel-testing-worker-number 往上拧,而是让该旋钮落在矩阵 A 与你 SKU 匹配的那一行。
远程 Mac 机群会放大错觉:当 Runner 分布在香港、日本、韩国、新加坡或美国时,开发者常以为延迟是瓶颈。很多时候只是东京机房 16 GB 上跑了四个模拟器——与弗吉尼亚四个模拟器行为一致。地理不制造内存 GB。
请把并发规划与 池容量与队列 SLO 交叉阅读,确保编排器里「ui-heavy」队列永远不会调度到内存不足的主机。对跨区团队,把「区域」与「内存档位」拆成两个标签维度,避免用单一 region 标签掩盖硬件差异。
若你在用自研调度器,建议记录每次作业结束时的模拟器设备 UUID 与磁盘增量,便于回溯「哪条流水线把池子撑爆」。这类数据在事故复盘时比「当时 CPU 不高」更有说服力,也能直接支撑采购或缩容决策。
编排器标签应如何命名才诚实
诸如 macos-latest 的友好标签掩盖了队列背后是 16 GB 还是 32 GB。应把内存等级与模拟器策略写进池名,例如 mac-m4-24g-ui-max2,让产品侧无法把重截图套件误排到只为编译吞吐采购的主机上。
标签变更需配套仪表盘:按小时绘制模拟器启动次数。若启动曲线快过合并请求曲线,说明在 thrash 模拟器而非测试代码——在高管问「移动交付为何在无害 Xcode 补丁后崩盘」之前先节流。把「启动次数 / 合并请求」做成 SLO 的辅助指标,比只盯队列深度更能提前预警。
值得写进手册一次的数字默认
- 空闲超时:无 XCTest 流量 20 分钟后关闭模拟器。
- 重试上限:每个 PR 的 UI 重试最多 2 次,避免掩盖系统性内存压力。
- 看门狗预算:每千次测试 SpringBoard 崩溃超过 0.5% 视为硬节流触发。
黄金法则:在每周 CoreSimulator 体积降到磁盘护栏以下且压缩内存跨夜间跑批保持平稳之前,不要提高并行工作者。
八步上线
- 度量基线:并行工作者、p95 UI 作业时长、CoreSimulator 目录体积。
- 打标主机内存档位;未经显式批准禁止在 16 GB 上打 UI 重标签。
- 实现流水线 teardown 中的关机钩子,成功与失败路径都要有。
- 排程每周抹除作业,若耗时超过十分钟要告警。
- 文档化回滚:单一环境开关即可把并行减半。
- 培训移动工程师:本地八模拟器笔记本不能证明 CI 容量。
- 配对截图与视频产物策略,避免磁盘夜间被灌满。
- 横向扩容:节流已诚实但队列仍违约 SLO 时,增加 NodeMac 主机。
FAQ
Apple Silicon 是否天生比 Intel 能塞更多模拟器?
每瓦吞吐更好,但内存仍有限。M4 消解的是 CPU 借口,不是物理。
模拟器是否应共享一个 DerivedData 卷?
更倾向每作业隔离 DerivedData 根以减少 inode 抖动;仅在纯编译主机上合并缓存。
哪里读 SSH Runner 相关指南?
调模拟器前先读 NodeMac 帮助中心 的远程接入模式,再处理高延迟链路上的稳定性。
实践补充:值班与合规视角
对受监管行业,建议在变更单里附上:调整前后的并行路数、CoreSimulator 体积截图、以及一次夜间跑批的压缩内存时间序列。审计方关心的是「你是否能证明变更是数据驱动」,而不是口号式的「我们优化了 CI」。把矩阵打印成一页纸贴在 on-call 手册旁,能显著缩短凌晨扯皮时间。若团队同时运行 Android 模拟器与 iOS 模拟器,请把两类工作负载拆池,否则内存模型会完全偏离本文假设。
NodeMac 客户在独占 Mac mini M4 上可为 UI 池单独购买内存档位,并把清理窗口与业务低峰对齐;这与「共享 Jenkins 老 Mac 靠人情重启」形成鲜明对比。把本页链接放进内部 wiki 的「移动 CI 入口」,新人入职第一天就能读到同一套语言,减少口口相传造成的配置漂移。
诚实的模拟器并行度才能让 Apple Silicon M4 的 GPU 与 Neural Engine 发挥作用——内存不再抖动时,UI 测试才会稳定结束而不是神秘超时。以 原生 macOS 配合 SSH 自动化与必要时 VNC 观察卡死模拟器,与设计师本地习惯一致。按区域 租用专用 Mac mini M4 可把 UI 池与编译池隔离,不必再堆笔记本。可预测的单机内存 胜过哀求同事重启共享 Jenkins。当矩阵证明需要新增 UI 专用主机时,请 按区域对比定价,而不是把模拟器无限堆到机群崩溃。