iOS 越狱真机实测:PageFault 与内存压缩/解压性能多维论证

全部结论仅来自本页引用的设备实测数据(测试用例 + 真实应用)与 XNU 源码锚点,二者互相交叉验证;无经验性断言。

0. 摘要(每条都有下文数据支撑)

  1. 次要缺页(零填充)成本稳定且可预测:16KB 页 ,4MB→256MB 工作集波动 < ,顺序/随机差异 < 4%;ru_minflt 增量与页数在全部 行严格相等(自校验)。
  2. 解压远快于压缩(字节导向算法):libcompression LZ4 在 mixed 数据上解压/压缩吞吐比 ≈ ;内核自研 WKdm 则近似对称( / MiB/s),这是它被选为内核缺页路径编解码器的原因之一。
  3. 内核压缩器是"最后手段"且受速率限制:内存充裕时(free 1.38GB)持续 20s 零压缩;8×384MB 突发压力下 2s 内 free 2.96GB→42MB,压缩器仅处理 (比率 ,与 WKdm 实测 TYPICAL 比率吻合)。
  4. 真实应用大规模依赖压缩器:挂起态支付宝 脏内存位于压缩器内(占其 footprint ),物理驻留仅
  5. 受害者选择按"年龄"而非"谁分配":6×320MB 新分配冷页在 1.95GB 全局压缩发生期间无一被压缩(重读 0 缺页);内核优先回收更老的应用冷页 —— 与 vm_pageout_scan 的年龄队列语义一致。
  6. 源码↔真机闭环验证:设备 vm.lz4_threshold=12288 与源码 arm64/16K 分支设定一致;wk/lz4_compressions 计数器恒 0 由 RELEASE 内核 VMCSTATS 编译屏蔽解释(非未使用)。

1. 测试环境与仪器

所有二进制(memperf/appsamp/decomp_probe*)均以 iOS SDK 交叉编译、ldid 注入 platform-application 签名后在设备上原生执行;采样通过 task_for_pid + task_infohost_statistics64/sysctl vm.compressor_* 完成,不依赖任何模拟。

2. 维度一:PageFault 微基准(测试用例 TC-PF)

方法:每次重新 mmap(MAP_ANON|MAP_PRIVATE) 后逐页写入 1 字节触发零填充次要缺页(16KB/页),mach_absolute_time() 计时、getrusage 计数,5 次重复取中位数;随机模式用 Fisher–Yates 打乱页序。

论证:① 缺页成本 ≈ 内核陷入 + VM 对象查找 + 16KB 零填充,其中零填充分带宽数量级(),因此 seq/rand 与工作集大小都不敏感 —— 缺页不是局部性问题而是固定成本问题;② 该数字是后续"解压缺页"成本模型的基线(§6)。

3. 维度二:压缩 / 解压吞吐(用户态编解码器 × 内核真实编解码器)

3.1 内核实际使用的编解码器:XNU arm64 WKdm(16K 页版本)

我们将 XNU 源码树中的 WKdmCompress_16k.s / WKdmDecompress_16k.s / WKdmData.s(即内核压缩器在 arm64 16K 页上执行的同一段汇编)直接汇编链接进测试二进制,在真机上测量内核真实路径的性能上限:

论证:WKdm 是字典+重排编码、无熵编码 stage,因此压缩/解压近对称();对 MOSTLY_ZEROS 解压高达 MiB/s —— 单值/稀疏页走 c_segment_sv_hash 单字存储路径(源码 vm_compressor.c L3952-3960),解压近似 memset。RANDOM 页被识别为不可压后原样存储(ratio=1.0,"压缩"吞吐 13857 MiB/s 实为检测+memcpy 成本)。

3.2 用户态 libcompression 矩阵(算法 × 数据形态 × 尺寸)

论证:① 同一算法家族内解压显著快于压缩(LZ4 mixed: MiB/s)——匹配解码无需搜索的事实;② LZFSE/LZMA 换来更差的移动端性价比:mixed 数据上 LZMA 压缩比 LZ4 慢 ×而比率仅持平(≈0.51,该数据集本身混合了不可压段);③ 尺寸 64KB→4MB 吞吐稳定(±3%),块压缩无跨块状态,内核按页压缩亦然。

3.3 zlib 等级权衡

论证:L1→L9 吞吐下降 ,比率仅改善 ;解压速率与等级无关( MiB/s 恒定)—— 等级只改变编码侧搜索深度。移动内存路径上高级别压缩得不偿失。

4. 维度三:内核压缩器何时被触发(多进程压力实验)

方法:fork N 个独立地址空间(绕过 iOS 单进程驻留上限),各自 mmap 并填充高压缩性 text 数据,父进程 2s 步进采样全局压缩器字节/页计数器。

论证:① control(6×288MB):free 下探 1.38GB 后立即稳定,20 秒内 vm.compressor_input_bytes 增量为 0 —— iOS 14.8 在空闲页充裕时完全不压缩,宁可把冷页留在 inactive 队列;② aggressive(8×384MB):2 秒内 free 2.96GB→42MB 触发安全停机,压缩器同期仅消化 (≈46MB/s 的突发处理速率),远追不上 ~1.5GB/s 的子进程缺页填充速度 —— 压缩器是滞后、限速的兜底,不是并发回收通道;③ 实测聚合比率 落在 WKdm TYPICAL 区间(§3.1 ratio ),佐证 bulk 路径主体走 WKdm 系编码。

5. 维度四:真实内核解压路径探测(老化 + 受害者选择)

方法 v1:6 个子进程各映射 320MB text 数据并触碰一次(成为冷脏页),等待压缩器稳定后令其逐页重读(每 16KB 页读 1 字节 = 恰好一次潜在解压缺页),全程读 vm.compressor_decompressionsru_minflt

论证(受害者选择):压力期内全局压缩了 1949MB,但我们这批"年轻"冷页一页未失(重读 0 缺页、0 解压)—— 内核按年龄优先牺牲更老的应用冷页。推论:应用退后台越久,其内存越先被压缩(与 §6 真实应用观测互证)。v2(75s 老化 + 不可压 wave2 施压,使我们页成为最老冷页)结果见下表,用于测得真实解压缺页成本并与 §6 成本模型对账。

6. 维度五:真实应用(微信 / 淘宝 / 京东 / 美团 / 支付宝 / Safari)

6.1 后台驻留横断面:谁在被压缩

论证:挂起应用普遍呈现 resident ≪ compressed 的形态(支付宝 resident vs compressed);前台活跃应用(Safari/Preferences)则相反。压缩器实质上承担了挂起应用的二级存储:脏页不落盘(iOS 14.8 无 swap,vm.compressor_swapouts_under_300s=0),全部就地压缩。

6.2 冷启动缺页风暴

论证:冷启动 万次缺页集中在首 秒,峰值速率 (单应用)—— 与微基准单线程 /s 同数量级,说明启动期瓶颈是缺页固定成本 × 页数,而非磁盘 I/O(pageins 远小于 faults)。

6.3 压力下的应用再激活(reactivation)

论证:设备级压力上升时,采样中的前台应用 faults 速率不降,但全局 compressor_decompressions / reactivations 增长加速 —— 被压缩的挂起应用在其(少量)被再次触碰的页上付出解压成本,而前台应用受 vm_pageout 保护。表:挂起应用在 6×384MB 压力(free 下探 348-518MB)期间完全冻结 —— faults Δ=0、压缩字节不变:它们早已被压缩过(稳态即完成),且未被唤醒。

7. 维度六:XNU 源码交叉验证(tag xnu-7195.141.2 = iOS 14.8)

论证闭环:① 混合编解码选择:metacompressor() 先 WKdm,失败或产出 ≥ 阈值再试 LZ4 择优(vm_compressor_algorithms.c L296-405);设备 vm.lz4_threshold 实测 12288 = 源码 arm64 16K 页的编译期设定(L453),配置与二进制一致。② wk/lz4_compressions 恒 0 的"反常":RELEASE 内核 VMCSTATS=(DEVELOPMENT||DEBUG) 把计数语句编译为空(L122-140)—— 计数器不可用 ≠ 算法未使用,不能据此判断 WKdm/LZ4 占比(此前多份分析踩过此坑)。③ 解压缺页路径:vm_compressor.c c_decompress_page L4077metadecompressor L4311 分派;每次线程解压计数 vm_fault.c L163(正是本报告 appsamp/decomp_probe 采样的那组计数器)。

8. 综合论证:成本模型与系统设计含义

次要缺页(16KB 零填充)实测成本
WKdm 单页(16KB)解压 @ MiB/s
解压缺页预测成本 = 缺页开销 + 解压
v2 实测解压缺页成本(若触发,见 §5)
系统设计含义(全部由上文数据推出)
压缩比换来的内存 ≫ 解压代价:真实系统生命周期比率 (input → compressed ),等效把 6GB 物理内存放大约 ×;解压一页 ~11µs 级,仅相当于 3.7 次零填充缺页 —— 对挂起应用这是纯收益。
压缩器不能救突发:~46MB/s 的实测突发消化率(§4)意味着 1GB 突发需要 ~20s 兜底窗口;app 的内存峰值必须靠 jetsam 阈值而非压缩器兜底 —— 与 Apple 对 app 的内存纪律要求一致。
缺页成本恒定 ⇒ 启动优化方向是减少页数(预触摸/按需分页/共享缓存),而非改善局部性(§2 seq/rand 差异 <4%)。
编解码器选型:内核选 WKdm(对称、单页独立、无熵段)契合缺页路径的同步小对象特征;用户态批量大块数据则应选 LZ4 家族(解压 16GB/s 级)。两者在本报告同机同数据上均有实测(§3.1/§3.2)。

9. 数据审计(机械复核)

以下恒等式在原始数据上程序化复核(site/audit.py),任何一条失败即整体判废:

10. 复现附录

设备与工具链

iPhone 12 Pro(D53pAP)· iOS 14.8 (18H17) · Darwin 20.6.0 · A14 6 核 · 6GB · Taurine 越狱 · 16KB 页。
macOS 侧:xcrun -sdk iphoneos clang 交叉编译 + ldid 签名;USB 经 iproxy 2222→22,paramiko 执行。
设备侧工具:/var/mobile/ios_perf/{memperf,appsamp,decomp_probe2}(本仓库 ios-perf-bench/bench/)。

测试用例清单

memperf pagefault:4/16/64/256MB × seq/rand × 5 reps,mmap 冷启动。
memperf compression:LZ4/LZ4_RAW/LZFSE/LZMA/ZLIB × zero/text/mixed/random × 64KB/256KB/1MB/4MB × 5 reps,往返 memcmp 校验。
memperf wkdm:XNU WKdm 16K 汇编(ALL_ZEROS/MOSTLY_ZEROS/TYPICAL/RANDOM)。
memperf mproc:N 进程 × per_mb × hold,2s 步进全局采样;free<96MB 安全停机。
appsamp:task_info(VM/EVENTS) + host_statistics64 + vm.compressor_* @250-500ms。
decomp_probe v1/v2:冷页老化 + 逐页重读,解压缺页计数。

原始数据文件(本站 data.json 来源)