01现象重载 vs 轻载,慢在哪一段
轻载基线 · 空闲充足(可用内存 ~5.3GB / 45%)
取景器首帧 801ms [749~810],n=5。申请内存"立取",管线建立与 App 孵化并行展开。
重载 · 空闲被压到 20~22%(可用内存 2.3~2.6GB)
与轻载等价部分 801ms多出的 +634ms
差额 +634ms 几乎全部落在内存路径:受理/孵化仅 +66ms,+568ms 在应用侧首帧链 —— 每笔内存分配都在排队等回收。
对照组 · iOS:重载不慢,证明问题是可解的
avail 压到 15% → 首帧 549ms(±0%),压到 8% 才 +14%。同样的 ~1.4GB 启动需求,其供给机制让相机分配从不等待回收 —— 重载变慢不是相机的固有成本,是内存供给的工程差距。
02根因链从现象逐层下钻到根因(每一步问"为什么慢?")
① 现象相机启动慢
- 重载首帧 1435ms vs 轻载 801ms,慢 +79%(+634ms)
- 慢的集中段:相机管线建立 → 首帧交付(重载轮帧交付推迟 +822ms,最大单项)
- 进程孵化本身几乎不慢(仅 +66ms)—— 卡住的是拿内存,不是跑代码
▸ p1 重载轮 vs 基线 n=5;tp1 hitrace/hiperf 下钻
为何变慢
② 直接原因内存申请慢
- 单笔分配从"立取"劣化为排队等回收挤空间,延迟放大百倍量级
- 量化根因 —— 速率失配:启动吞吃 1012~1404 MB/s vs 回收有效追赶仅 59~62 MB/s(1/17~1/24)
- 同样的建管线工作被拉长数倍:启动窗口 3574 笔分配事件,分配器 CPU 占比 16.8%(轻载不在榜)
▸ hiperf: libc++ 分配器上榜;OpenCamera 起步推迟 ~200ms
为何排队
③ 深层原因申请量大 → free 被击穿
- 启动瞬时集中申请 ~1.3GB+:会话 ION 缓冲池每次启动现建(稳态全池 ~1.8GB)+ 应用 ~200MB
- 重载下系统可用仅 2.3~2.6GB,这口需求瞬间打穿低水位("击穿" = 可用内存跌破动态水位线 ABM)
- 击穿之后,每笔分配都必须先回收 —— 申请与回收被串到同一条路径上
▸ 实测 MA 轨迹: 2345~2566 → 谷底 931~1137MB,一度跌破 ABM
击穿之后
④ 根因击穿后,回收慢
- 击穿触发同步直接回收(direct reclaim)挂在分配路径上:申请者必须等回收还页
- 重载下干净页早被压载逐出,只剩匿名页 → 只能 zswap 换出;对象是随机不可压数据,压缩零增益
- 0.7s 内搬 880MB(>1GB/s)纯烧 CPU,有效还页仅 59~62 MB/s → 队列越排越长
- 对照:iOS 同样回收 1.4GB,但并行压缩、相机页豁免、不在分配路径 → 首帧仅 +75ms
▸ p1b: T0→+0.7s 净换出 +880MB;M3 后反向排空 −400~−900MB/s(余震)
深挖:③ 的"申请量"从哪来?
恶性循环:"申请慢" 倒逼 "预分配",预分配放大申请量
内存申请量大的主要来源,正是为了规避"申请慢"而做的预分配
闭环 · 越转越慢
常态:逐笔申请慢且不可等
传感器 33ms/帧连续出帧,ISP DMA 直写缓冲 —— 管线不能停下来等 AP 慢慢分配
于是
设计对策:预分配
启动时一次性建全量会话 ION 池(~1.8GB),把后续所有帧缓冲提前拿到手 ——"宁可先占,不可用时缺"
推高
后果:瞬时量 ≫ 即时需求
启动一口气要 1.3GB+,远超当帧真实用量 —— 轻载无感,重载即成击穿 free 的主力
重载下
闭环:更慢 → 更依赖预分配
击穿 → 回收慢 → 申请更慢 → 运行期更不敢逐笔申请,下次启动预分配更狠
恶性循环:申请越慢越预分配,越预分配越击穿
04优化方向方向一治本(不击穿就不进慢回收),方向二兜底(击穿了也快速回血)—— 两者正交,可组合
方向一 · 优化内存申请量源出 ③ + 恶性循环
治本 —— 不击穿 free,就根本不进入慢回收。目标:启动瞬时物理占用 ≈ 真实即时用量
破法 A · 正向优化预分配量
- 实测精简:按会话各池真实峰值用量裁剪容量/块数,砍掉"以防万一"的超配
- 分级分波预建:首帧必需的池先行;长焦/IR 等非关键池取景稳定后再补建(参考 iOS 实测的 4 波渐建池,错峰更彻底)
- 保留"管线不等分配"的确定性,同时把瞬时申请量压到贴近真实峰值
破法 B · 缺页机制:让预分配"失效" ★ 核心方案
- 预分配降级为虚拟预留:启动只注册池槽位/保留地址空间,不落实物理页 —— 零实际占用
- 真正用到才缺页:槽位首次被写入(ISP 出帧)时才按需分配物理页(demand paging)
- 名义池全量存在,物理占用与真实使用 1:1 对齐、随用随占 → 启动不再击穿 free
- 实证:供给充足时缺页即拿页,速率 5.0~6.2GB/s ≈ CPU 速度、零回收参与 —— "用时才拿"不慢,慢的是"拿时先抢"
注意:首帧关键页可少量预热 touch,规避首次缺页抖动
方向二 · 优化内存回收速度源出 ④
兜底 —— 即使被击穿,也快速回血。目标:direct reclaim 单次耗时 ↓,分配排队 ↓
1回收前置 / 异步化
启动意图出现即预排水、拉高水位,把回收搬出分配关键路径(kswapd 化)。iOS 即此路线:"杀别人保相机",回收与申请并行 —— 相机从不等回收
2回收路径提速
优先回收干净文件页/可丢弃页;不可压缩数据不进 zswap(实测 >1GB/s 换出纯烧 CPU,有效还页仅 59~62MB/s);压缩/换出多核并行
3关键页豁免回收
相机自有缓冲锁页/常驻化,不与全局回收同池竞争 —— iOS 实证:相机页全程不参与压缩,1.4GB 回收工作首帧仅 +75ms
组合建议:破法 B 从源头消掉击穿(量随用随占),方向二保证长尾重载下也有快速回血 —— iOS 两手都有,故 avail 15% 仍 ±0%。