如何合并 multi-generational LRU

Merging the multi-generational LRU很多类型的内核改动都可以在邮件列表中讨论决定下来。然而,有些类型的 patch 就很难通过这种方式完成实现。它们非常大,而且具有侵入性,需要相关开发者的实际聚在一起讨论。multi-generational LRU 工作(MGLRU)就属于这种类型,这就是为什么它是 2022 年 Linux 存储、文件系统、内存管理和 BPF 峰会(LSFMM)上一整个小时会议的主题。在那里进行的讨论很可能为这个代码打开了大门,可以在不久后合入 mainline。

MGLRU introduction

该会议由 MGLRU 工作的开发者 Yu Zhao 主持。他首先说,RAM 的性能将继续在我们整个系统的性能中扮演重要角色。他说,要获得最好的性能,就需要假装我们的内存比真实情况更多(overcommitting memory),这就进而导致了一些问题。问题之一就是要决定在任何一个时刻应该有哪些对象位于内存之中;这就是 MGLRU 的作用了。另一个活跃的探索领域就是希望增加可驻留在内存中的 page 数量——例如通过碎片整理(defragmentation)和 zram —— 但 Zhao 在这里就不涵盖这一方面了。

他提供了关于 MGLRU 的介绍,在之前的 LWN 文章中也有描述。MGLRU 的核心思想是将内存分为若干个 bucket,称为 "generations (世代)"。一个 page 的 generation 值就反映了它的 "年龄"–距离该 page 被最后一次访问有多长时间了。这些 page 的管理是由一个被 Zhao 描述为 "两个表针的时钟" 的机制完成的。aging 这个指标会扫描 page 的 accessed bit,看它们自上次扫描以来是否被使用过。被使用过的 page 就被标记好等待搬移到最年轻的一个 generation。eviction 指标则实际上会将 page 移到正确的 generation;那些最终进入最古老的 generation 的 page 是最少访问到的,可以考虑进行回收。

MGLRU 背后的一个有趣的设计决定是,它的扫描是通过遍历进程的 page table 来进行的,而不是扫描物理内存。这在一定程度上是为了提高效率,Zhao 说;目前内核中的 LRU 查找必须不断地在不同进程的 page table 之间切换,这就造成了 cache miss,从而影响性能。不过,查找 page table 有一个问题是,它们可能非常稀疏,有很多空的条目;扫描这些条目不会有任何价值。因此,MGLRU 代码包括一个 Bloom filter 过滤器,帮助它避免去查看那些包含非常少的 active entry 的 page-table page。MGLRU 代码还试图从错误中进行学习,也就是关注是不是它回收的 page 中有一些很快又被加载到了内存里。为此,它加入了一个 proportional-integral-derivative (比例-积分-衍生,也就是 PID)控制器,以便在它做出了可能错误的决定时来重新思考一下。

Johannes Weiner 在讨论中首先询问了 aging (老化)是如何进行的。如果看到最古老的 generation 的 page 被访问了,那么它是被移到最年轻的一代,还是只是只是移到比当前这个 generation 年轻一代的位置?Zhao 回答说,这实际上取决于访问的类型。如果该 page 是通过 page-table 访问的,那么它就会被移到最年轻的一代;相反,如果是通过文件描述符来访问的,那么它只会移动到相邻的一代。这有两个原因:对那些 file-backed (文件对应)的 page 进行 evict 的代价比较低,而且系统可以看到每一次访问(因为它们是通过内核完成的),而通过 page-table 的访问只能在每次扫描中被观察到一次。Andrew Morton 问道,这个 page 的 dirty 情况是否被考虑在内;答案是 yes,dirty page 会被上移一个 generation。

Extensions

Weiner 继续发言,一般来说,generational garbage-collection algorithms (代际垃圾收集算法)会查看这个对象已经被使用了多长时间。所有的东西都从最古老的一代开始,如果被使用过的话,随着时间的推移被回收的可能性就会变小。不过,MGLRU 是从最年轻的一代开始的,也会直接把 page 都 promote 到这里。他问道,以这种方式管理各个 generation 的目的主要是什么?

答案有点令人惊讶:似乎是在各个 generation 间移动 page 的完整机制还没有到位。Zhao 说,当 MGLRU 第一次发布时,它被称为一个 "framework"。现在有很多不同的使用场景,从服务器到手机等等各式各样,即使在手机这样的单一场景中也有很多细分种类。想出一个在所有地方都适用的 generation 分配算法将是一个困难的挑战,所以 MGLRU 允许定制。会有 BPF hook,在每个需要分配 generation 的 page 时候来进行调用,这些 hook 会看到相关进程的 PID、page address、type(anonymous 还是 file-backed),以及对于 page fault 的情况会提供 fault 的类型。然后,被调用的 BPF 程序就可以告诉内存管理子系统,这个 page 应该被放在哪一代。Zhao 继续说,网络子系统最开始时只有一个拥塞控制算法,随着时间的推移,这种算法的数量不断增加,现在它们的实现正在向 BPF 转移。他说,MGLRU 也正走在同样的道路上。

Weiner 承认,他还不知道 MGLRU 的这一个计划。Zhao 说,这个机制不在当前发布的 patch 之中,但应该会加入进来。

Zhao 继续说,未来的 MGLRU 可能会加上检测具有 transparent huge page 的内部碎片(internal fragmentation)的功能。现在有很多应用都建议关闭这个功能;如果他们的内存访问都是 sparse 形式(非常稀疏的),那么使用 huge page 就会导致浪费大量的内存。他说,Redis 和 memcached 是受此影响的应用之一。问题是,对单个 base page 的访问就会使整个 huge page 显得经常被访问,可能会在一个 huge page 中浪费最多 511 个 base page 的空间。

huge page 的 internal fragmentation 的检测机制可以是一上来就用 base-page entry 来进行 mapping,然后用 MGLRU 观察访问规律。如果大部分的 base page 最终都出现在年轻一代中(也就是被使用过),可以将 mapping 转换成一个 huge page mapping;否则的话,可以直接把那些未使用的 page 回收掉。Michal Hocko 询问现在是否存在这种代码。答案是 "没有,但在未来四年内有可能发生"。然后,Hocko 建议把重点放在现在要做的这部分代码上。在收回话题之前,Zhao 又提到 virtual machine 的 ballooning 是另一个潜在的新功能,可以允许未使用的 page 被拿走。

Enabled by default

Hocko 说,在邮件列表中他已经表达了对 MGLRU 的担忧,这些情况应该如何处理。他说,MGLRU 有一些很好的扩展潜力,但是目前的 LRU 实现已经被改进了很多年,并受益于积累下来的大量经验。他建议,MGLRU 可以与现有的 LRU 共存,也就是采用 opt-in (可选性地加入)的方式。他说,merge 进来才是找出 MGLRU 在不同 workload 中真正工作得如何的结论的唯一方法,但他对默认切换过去感到很担忧。

他继续说,也许可以考虑默认启用;这会是对代码及其开发人员的一次 "trial by fire"。显然,有必要明确告诉用户如何关闭这个功能。他说,两种方法都有好处,维持两个 LRU 的时间越长,就会有越大的成本。他问,我们大家能达成什么样的一致意见?

Mel Gorman 说,如果这个代码被合并的话,就应该被默认启用。尽管如此,他担心大多数发行版将无法启用它,因为 MGLRU 目前对内核所能支持的最大 CPU 数量有一个限制,比较低。Zhao 说,这个限制将在下一版本的 patch set 中被移除。CPU 数量相关的问题显然是跟 MGLRU 需要 page flag 的数量相关的;Zhao 提出有一个方法来释放一些 page flag,这引起了大家的好奇心和注意。随后,大家就如何做到这一点进行了讨论,但没有得出任何确定的结论。

Gorman 把话题又引了回来,他说测试表明,MGLRU 的性能挺不错;不过在 single-node 机器上的性能表现比在 NUMA 系统上更好。

Morton 说,如果这个代码希望能成功,它就应该从相对较少的用户来开始。随着时间的推移和问题的逐步解决就可以变得更好,然后人们可以开始转而使用它。但他担心,MGLRU 代码的开发历史是隐藏起来的,而且代码本身有些 "不可捉摸(inscrutable)";他建议多花时间来写一些内部机制的介绍文档。Morton 说,Zhao 需要 "讲一个故事",从而使开发人员了解情况。

Morton 继续说,目前的 LRU 对于某些工作负载来说似乎仍然是有优势的。内核仍然有多个 slab allocator,但他希望今后不要再这样做了;应该让 MGLRU 对所有 workload 都要更有优势。但是其实我们对文件系统没有这种担心;我们鼓励用户来自己选择文件系统。也许对 LRU 也可以这样做。

他最后说,他设想可以 "在下一个开发周期" 就合并这些代码,但这将是一个挑战。添加 MGLRU 将会把那些在内存管理方面工作了几十年的开发人员 "变成了新员工",他们将不得不面对一个复杂的代码库,没有注释,而且周围没有人可供咨询来回答他们不明白的东西。

Zhao 说,他很难在没有完成 upstream 的情况下让更多用户尝试 MGLRU。显然,甚至在谷歌内部都是无法做到。他的小组最后雇用了一家外部公司来做这个代码的 benchmark 工作。

"Expect a few bug reports"

Hocko 说,如果这段代码被合并,那估计没有人反对默认启用它。他告诉 Zhao,当走到这一步的时候,"预计会有错误报出来",并问 Zhao 是否准备好了这里可能有大量的工作。如果他解决问题的速度不能跟上问题出现的速度,那么他是否做好准备看到整个工作被 revert 掉?Morton 建议在 linux-next 中启用 MGLRU 试一天,看看会发生什么,但 Gorman 说,在这么短的时间内不可能得到有用的信息,"除非完全是不可用的"。他说,真正的内存管理问题往往更加微妙,需要更多的时间才能显现出来;发现这些问题的唯一方法是让主要的发行版提供商都默认启用 MGLRU。

Zhao 试图向大家保证,谷歌将继续支持这项工作;他说,一旦合并,安卓和数据中心这边都会开始使用 MGLRU。他说,已经做好了未来几年的预算规划。他也一直在与其他公司进行一些私下合作,请他们使用它,并会继续这样做。希望他最终能够休息一下,总有这一天的,但肯定会继续很长时间。

Morton 说,就算将其默认禁用,也仍然可以有很大的价值,尤其是如果谷歌能够花资源来进行测试和改进。Gorman 认为没有什么好的其他选择;如果不默认启用,大多数人就不会使用它。他不反对合并这段代码,但希望看到它被默认启用。他说,当合入 transparent huge page 的时候,该功能就是被默认启用的,并 "到处都出现问题"。人们花了三年时间来解决这些问题,但如果没有对所有用户启用,这些问题就永远不会被 fix。

Zhao 说,他更愿意遵循谷歌公司使用的流程,即从一小群用户开始,慢慢增加。Gorman 回答说,这样的方法对一个团队(fleet)来说是好的,但它不能帮助那些主流发行版提供商来决定何时进行转换。即使有些东西在整个团队中有效,也不意味着它在更普遍的情况下也会有效。Weiner 说,一些发行版提供商会打开 MGLRU,即使它被默认禁用;他特别提到了 Arch。这可能是避免 "total flag day" 的一个好办法。Matthew Wilcox 表示同意,他说默认启用 transparent huage page 实际上是个错误做法。他说,文档永远都在,而供应商仍然在告诉用户要禁用 transparent huge page,尽管这些问题早已被修复。

Zhao 说,他可以默认使用 MGLRU,但这样一来就可能会对其他人是个问题;如果这个机制带来了破坏,用户就会受到影响。所以他认为这个方法风险太大了;在一年后再默认切换到 MGLRU 可能更好。Weiner 说,如果默认关闭 MGLRU,应该预先设定好大概多长时间之后会默认启用,比如最多会等几个开发周期。

讨论到此为止,相当疲惫的内存管理开发人员们结束了会议,也结束了今天的所有议题。看起来这项工作极有可能在不久的将来被 merge,尽管 Morton 提出的在 5.19 版本中就合入的建议可能让其他人觉得有点仓促。不过,是否会对所有的用户默认启用,暂时还不清楚。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值