背景

MGLRU 在回收 large folio 时,要检查并清除每个 PTE 的 young 标志(用来判断这页最近是否被访问),决定该 folio 的去留。旧实现调 ptep_test_and_clear_young_notify() 逐 PTE 顺序处理,对 large folio 来说很慢。

┌──────────────────────────────────────┐
│ MGLRU 回收大页时逐个表项查清访问标志 │
└──────────────────┬───────────────────┘
                   ▼
┌──────────────────────────────────────┐
│         逐表项处理对大页很慢         │  连续表项优化只覆盖一小段,大页可能超出
└──────────────────┬───────────────────┘
                   ▼
┌──────────────────────────────────────┐
│  更新 generation 也逐表项查访问与脏  │
└──────────────────────────────────────┘

Arm64 支持 contiguous PTEs,其专用 ptep_test_and_clear_young() 已对连续范围内的 PTE 批量清除 young 做了优化。但 large folio 可能超出一个连续范围(CONT_PTE_SIZE),这点优化不够。MGLRU 遍历进程页表更新 folio generation 时也逐 PTE 检查 young 与 dirty,同样低效。

问题

  • MGLRU 回收 large folio 逐 PTE 检查/清除 young 标志
  • Arm64 连续 PTE 优化只覆盖 CONT_PTE_SIZE,large folio 可能超出
  • 逐 PTE 处理在回收热路径上低效
  • 遍历页表更新 generation 也逐 PTE 检查 young+dirty

方案

核心是给 large folio 引入批量 young 标志处理,一条调用覆盖整个 folio 的 PTE 批(可能超连续范围)。

旧:逐表项查清访问标志
┌──────────────────────────────┐
│    大页的每个表项单独处理    │
└──────────────┬───────────────┘
               ▼
┌──────────────────────────────┐
│ 连续表项优化覆盖不到整个大页 │
└──────────────────────────────┘

新:整批表项一次处理
┌────────────────────────────┐
│ 新批量接口一次覆盖整个大页 │
└─────────────┬──────────────┘
              ▼
┌────────────────────────────┐
│  架构落地更高效的批量实现  │  回收与更新 generation 都用批量,计数偏差可忽略
└────────────────────────────┘

系列沿「批量 helper 到 MGLRU 集成再到 arm64 落地」这条线推进。

核心是引入批量 helper test_and_clear_young_ptes() 及其 wrapper test_and_clear_young_ptes_notify()(与现有函数接口一致),让架构有机会实现更高效的批量操作。

接入 MGLRU 分 2 个用点:回收 large folio 时用 test_and_clear_young_ptes_notify() 批量检查与清除 young;MGLRU 遍历页表更新 folio generation 时也改成批量,一次同时检测整批 PTE 的 young 与 dirty。旧的逐 PTE 接口接入完成后已无用户,直接移除。

架构落地由 arm64 完成:实现专用的 test_and_clear_young_ptes()(用 pte_cont 简化),让批量真正高效。

一处取舍要讲清:批量操作可能让 young 计数偏高(large folio 内并非所有 PTE 都被访问)。这个计数只用来决定某个 PMD 是否值得再次扫描,且要经过 bloom filter,而 bloom filter 本就允许误差;另有一个纯调试的 young 统计同样可能偏高,没有任何功能影响。

触发 look-around 的 folio 自身也按整批计入这个计数:它在函数入口就被批量 test-and-clear,accessed 位已清,随后的循环不会再统计它,此前却只固定贡献 1(批量化之前的遗留),与窗口内其余 folio 的口径不一致。按整批计入同样是有意高估:批内具体几个 PTE 被访问不可得,高估顶多让 PMD 被多扫一轮,低估却可能漏扫仍有热页的 PMD,使其被错误回收。

收益

作者在 Arm64 32 核服务器和 X86 机器上测试(启用 MGLRU,mmap 分配 10G 干净 file-backed folios 到 memory cgroup,经 memory.reclaim 回收 8G file-backed folios):

平台 W/o patchset W/ patchset 改善
Arm64 32 核(reclaim 8G 耗时) 0.470s 0.180s ~62%(60%+)
X86(reclaim 8G) ~15%

Arm64 上批量 young 标志处理把回收 8G file-backed folios 的耗时从 0.470s 降到 0.180s(60%+);X86 上也有约 15% 改善。