⓪三句话结论
① 回收时机——两家同构:都是惰性、按需、文件页优先;iOS 是"瀑布+预算"(每轮最多抓 2 个匿名页),Linux 是"比例扫描"(swappiness 动态配比)。
② 写侧——哲学相反:iOS 把压缩做成隔离的后台服务(2 个专职线程、E 簇、异步队列、背压,App 永不自己动手);Linux 把压缩做成回收的内联步骤(谁分配内存谁自己压)。
③ 读侧——架构收敛:解压都发生在缺页线程自身(消费者出力、只拆摸过的、空页直接跳过);iOS 的同步 pager 是起点,Linux 用 v4.15 的 swapcache 旁路补丁花八年演化到同一形态。
通俗版:把内存想成真空收纳袋。要用时:两家都是"谁用谁自己拆、只拆用到的、空袋跳过"。差别是收拾时——iOS 雇了两个专职打包工在省电的后屋慢慢打包(前台永不帮忙、装不下就扔整箱);Linux 不雇人,谁要新货架谁就自己先打包点旧货腾地方。手机选前者,机房选后者。
①匿名页的四条出路
匿名页(堆、栈——没有文件 backing 的页)是内存压力的主体。两家给它们的"退出通道":
| iOS(xnu-7195) | Linux 5.10 |
| 第一通道 | purgeable volatile 摘账(§8 token 状态机) | —(等价物 MADV_FREE 延迟清理) |
| 主通道 | 压缩器:WKdm/LZ4 逐页压缩进 c_segment 段,纯 RAM | zram/zswap:LZ4 等压进内存池(zram)/池+LRU+盘分层(zswap) |
| 冷通道 | freezer:整进程映像落 NAND(band 75 挂起 App 专属,每日 1024MB 预算 SRC) | 页级 swap 到磁盘(zswap writeback / 真盘 swap) |
| 兜底 | jetsam:按 band 杀进程("内存不够时放弃谁"是系统先验) | OOM killer(按 oom_score 杀) |
分层哲学的分叉:Linux 的第二层是页级磁盘分层(冷热是连续谱);iOS 的第二层是进程级冻结(生死是离散事件)。这决定了:zswap 池 miss 时一次换入变成毫秒级磁盘读;iOS 的换入永远是微秒级 RAM 解压,冷热只影响"要不要冻结整个进程"。
②回收策略:选谁、压多快
选谁
| iOS | Linux 5.10 |
| 策略形态 | 瀑布 + 预算:文件页优先,匿名预算 2 页/轮(ANONS_GRABBED_LIMIT=2,vm_pageout.c:1462;:2423-2466 判定)SRC | 比例扫描:swappiness + IO 成本模型动态决定 文件:匿名 配比(get_scan_count) |
| 压缩积压时 | 强制转文件页(vm_pageout.c:2151 "keep stealing those")——背压保护SRC | 无对应概念——积压表现为分配卡顿(direct reclaim stall) |
压多快(真机实测)
| 场景 | 压缩供给速率 | 依据 |
| 文件页充足阶段(政策节流) | 25 MB/s | EXP vmwatch 50ms 窗 + 相机 launch/回收实验(2026-09-02/07,raw-data/cam0902_*、anonreclaim_roundtrip_0907) |
| 切换到匿名阶段(全速) | 926 MB/s |
| 相机启动 12s 毛值(1s 聚合) | 719 MB/s(毛 1474) |
| 政策差 | 35×——是政策不是硬件 |
净压缩比:daemon 匿名 3.06×,五 App 混合 2.03× EXP。守恒闭环残差 4%。
③写侧(压缩):两种哲学的对决
iOS:vm_pageout_scan(专职扫描,绝不自己压缩)
│ 匿名受害页投入队列(vm_pageout.c:727)
▼
VM_compressor ×2(BASEPRI_VM=91,软绑 E 簇 :4332;懒唤醒 :4002)
└ c_compress_page:3844 ─ 段预分配·bump 槽位·逐页选码·页→段槽【一次搬运】
背压:队列满 → 扫描器转向文件页或休眠
Linux:kswapd(后台,普通内核线程) 或 分配线程自己(直接回收 try_to_free_pages, vmscan.c:3233)
└ zram __zram_bvec_write:same_filled 检查 → per-CPU 流压缩进 scratch【搬运①】
→ zs_malloc 两阶段分配(失败→放流→允许回收→compress_again 重压缩,writestall 计数)
→ memcpy scratch→zs 对象【搬运②】——全程在回收调用者上下文,无队列无背压
六差异速览
| 维度 | iOS | Linux 5.10 |
| 付账主体 | App 永不压缩(缺页只睡觉等页) | 分配线程自己压(cgroup 记账精确,但前台会遭遇 direct reclaim stall) |
| 并行度 | 封顶 2 线程(实测常 1 忙,懒唤醒)EXP | =并发回收任务数,可到 N 核 |
| 搬运次数 | 1 次(直写段槽) | 2 次(scratch→zs 对象)+ kmap×2 |
| 编解码选择 | 逐页四选一:SVP/WKdm/LZ4/原文(metacompressor, vm_compressor.c:3891) | 设备级单算法(mkswap 时定死)+ same_filled 前置 |
| 存放布局 | 定长段预分配 pin 死 + bump 指针 + 专职 compactor | zsmalloc size-class 逐页分配(分配本身是二号成本) |
| 功耗/优先级 | E 簇低功耗核、系统高优先级 | kswapd 普通优先级;direct reclaim 跑在 App 自己的 QoS 上 |
实测吞吐 EXP:MZV 纯零页波 1310 MB/s(9.2:1);真实数据波 975–994 MB/s(1.30:1);每核 ~810 MB/s,与 WKdm 公开基准 774–793 MiB/s 两链互证;压缩期 E 簇 0.8–1.1 核忙、P 簇≈0(E 簇绑定真机兑现)。
结构判词:iOS 把便宜的事(读)交给市场,把昂贵的事(写)收归计划——写要动全局状态(段、槽位、盘),集中才有背压;读只碰已冻结的数据,分散无损。Linux 读写都是市场经济,靠 N 核弹性换保护缺失。
④读侧(解压):为什么这么快,以及趋同
真机实测(抖音前台回程)EXP
| 指标 | 数值 | 注 |
| 解压峰值(50ms 窗) | 2552 MB/s | dc 计数器每页递增(vm_fault.c:2396),非批处理——真测量 |
| 惰性召回 | 149 / 521 MB(29%) | 只解压摸过的页;190MB 未触碰永留压缩器 |
| 同秒事件 | 相机拆除 wire −827MB | 回程与拆除同窗——供给来自两处 |
快的五层原因
| 层 | 机制 | 锚点 |
| ① 算法方向 | 压缩是搜索(依赖读+分支),解压是数据流(顺序 store,近 memcpy)——天然便宜 1.5–2× | GEN |
| ② 硬件快道 | 同值页(零页大头)走 SVP:哈希表 + fill32_dczva 硬件缓存行清零,成本≈memset | vm_compressor.c:4521, :4035 SRC |
| ③ 并行 | 解压在缺页线程自身——App 恢复时六核并行 fault;写侧只有 2 线程 | EXP+SRC |
| ④ 路径纯度 | 零 I/O、pinned c_segment 直指针、段锁分片无碰撞 | SRC |
| ⑤ 惰性 | 需求限速而非能力限速——"有效 250MB/s/2s"是 App 按需取货的速度 | EXP |
读侧对比:架构收敛
| iOS | Linux 5.10(zram 同步旁路) | 判定 |
| 谁解压 | 缺页线程同步直返 | 缺页线程同步直返(do_swap_page 旁路, memory.c:3280) | 同构 |
| 同值页 | SVP 哈希表 + DC ZVA(无锁) | ZRAM_SAME + memset_l(槽锁) | 同构 |
| 不可压页 | 原文存槽,读=拷贝 | size==PAGE_SIZE,读=memcpy | 同构 |
| 惰性召回 | 只解压摸过的(29% 实测) | count==1 才旁路,单页懒取 | 同构 |
| 页宽 | 16KB(64 次/MB) | 4KB(256 次/MB)——4× 固定手续费 | iOS 优 |
| 中间层 | 0(段直指针) | bio 包装 + zsmalloc 映射 + 槽位锁 | iOS 优 |
| 单次延迟 | ~13–25μs/16KB | ~6–9μs/4KB EST | Linux 优 |
每 MB 总账 EST:Linux ~1.5–2.3ms(256 页 × 固定开销为主)vs iOS ~0.9–1.6ms(64 页 × 解码为主)——LZ4 解码快 2 倍也补不回 4 倍手续费,iOS 每 MB 约省一半 CPU。
⑤换入路径:swapcache 旁路的八年故事 SRC
Linux 为磁盘设计的换入路径核心是 swapcache(中转登记处,四职责:共享同步点/回写一致性/预读容器/槽位生命周期)。对 zram 而言四职责三个半不成立,但通用路径不挑客户照单全收。
旧路径七步(v4.14 存档行号)——即使"盘"是 zram 也全套照办
| 步 | 动作 | 性质 |
| ① | lookup_swap_cache 查找 miss(v4.14/mm/memory.c:2886) | 记账 |
| ② | swapin_readahead 包装+窗口计算(swap_state.c:554) | 记账 |
| ③ | 二次查找→分配→radix 预载→树锁插入+SWAP_HAS_CACHE(:364-432) | 记账 |
| ④ | swap_readpage→bdev_read_page→zram 解压(page_io.c:350,377) | ★真正干活 |
| ⑤⑥⑦ | trylock/解锁循环→lock_page_or_retry(:2924)→写脏时第三次树锁 | 记账 |
提交 0bcac06f27d7 的 profile 原文:zram lz4 换入 S/W 开销 >70%——记账比干活还贵。
v4.15 旁路(2017-11-15,Minchan Kim)
双条件:设备同步(SWP_SYNCHRONOUS_IO——swapon 时有 fops->rw_page 才置位,swapfile.c:3242)且引用数==1(无共享者,不需要 cache 做同步点)→ 跳过 swapcache 直取。净效果:0 次 radix、0 次树锁、0 次睡眠,预读一并免掉。收益:5GB 顺序换入 2.41s→1.64s(时间 −32%/吞吐 +47%,提交自述 45% 系吞吐口径近似)。
九年维护账单——绕开全局机制的真实价格
| 时间 | 提交 | 性质 |
| 2021-06 | 2799e77529c2 | do_swap_page 与 swapoff 竞争 |
| 2022-04 | e914d8f00391 | zram 零页映射(数据损坏级) |
| 2023-06 | 1235ccd05b6d | per-VMA 锁适配 |
| 2024-02/03 | 13ddaf26be32 / 25cd241408a2 | 跳 cache 竞争 / zswap 数据丢失 |
| 2025-01 | 1dd44c0af4fa | shmem 复制同款旁路(收益大到值得复制) |
| 2026-02 | ae1a645def13 | 移除 zswap workaround |
XNU 镜像:iOS 的 compressor pager 从第一天就是同步接口(vm_fault→data_request 直返),根本没有"cache 可绕"的概念,也就没有这九年的 bug 账单。iOS 是起点,Linux 是八年演化——旁路本质是"给磁盘时代的统一柜台开 RAM 客户免登记通道",而 iOS 从未建过那个柜台。
⑥总判定
| 维度 | 判定 | 一句话 |
| 架构设计(读侧) | 打平 | 五层结构完全同构;Linux 的晚到反证 iOS 模型的正确性 |
| 每 MB 成本 | iOS 优 | 16K 页宽 + 零中间层 ≈ 每 MB 省一半 CPU EST |
| 单次缺页延迟 | Linux 优 | 小块 + LZ4 快——高频小包 vs 低频大包 EST |
| 写侧隔离 | iOS 优 | 前台零压缩税、E 簇能效、背压自适应(实测无失速点) |
| 写侧吞吐弹性 | Linux 优 | N 核并行扩展 + cgroup 记账公平 |
| 容量弹性 | Linux 优 | zswap 页级盘分层是 iOS 没有的能力(iOS 只能冻结/杀进程) |
| 同值页快道 | 打平 | SVP 无锁+DC ZVA vs SAME 无界——各擅其场 |
场景化结论:手机上 iOS 的解压路径是更优解(省电、快、可预测——Android 全系用 zram 同构方案算是行业投票);服务器/重负载上 Linux 优(内存超物理几倍时它有"地下室"可以续命,iOS 只能开始杀进程)。解压这件事两边已经想到了一起;剩下的差别不是谁更聪明,而是为不同世界做的取舍——iOS 为口袋里的设备省每一毫安,Linux 为机房里的设备留出无限货架。
⑦诚实边界与数据指针
- Linux 侧全部为源码走读(v5.10 原始文件 + v4.14/v5.4/v5.6/v5.7 界碑存档,均已入 git),无 Linux 设备实测——速率对比标 EST/GEN;LZ4/WKdm 解码速率取公开基准与 iOS 实测推导。
- iOS 侧数字来自 iPhone 12 Pro 越狱实测:
raw-data/cam0902_s2_timeline.txt(55 列 camwatch)、raw-data/anonreclaim_roundtrip_0907.txt(回程解压实验)。
- 50ms 窗压缩"峰值"存在计数器批处理伪影——压缩速率一律用 1s 聚合;dc 计数器每页递增相对可信(vm_fault.c:2396)。
- 50ms 窗峰值 2552MB/s 的并行度分解(2 核 × 1.3GB/s vs 3 核 × 0.85GB/s)本实验无法分离(未采样 CPU)。
- 主书对应章节:§5.11–5.13(相机/压缩归因/文件瀑布实测)、§14.6(换入路径对比)、§6.3–6.7(压缩器与"不用 swap"决策链)。