关注了就能看到更多这么棒的文章哦~
Multi-size THP creation, two different ways
By Jonathan Corbet
February 13, 2025
Gemini-1.5-flash translation
https://lwn.net/Articles/1009039/
Huge pages (巨页)可以提升许多程序的性能,但它们本身也可能带来不利的性能影响。在过去的几年里, multi-size transparent huge pages (mTHPs) (多尺寸透明巨页)越来越被视为一种折衷方案,它以较低的成本带来了巨页的优势。然而,如果系统不创建 mTHP,就无法从中受益;两位开发者分别发布了补丁,以在后台启用 mTHP 的创建。
Huge pages (巨页)
Huge pages (巨页)是由 CPU 及其 memory-management unit (内存管理单元)实现的一项功能;它使系统能够以混合的页面大小运行。然而,这些大小在数量上受到限制,并且完全由 CPU 的内存架构定义;它们对应于 page-table hierarchy (页表层级)的级别。作为提醒,该层级结构大致如下(来自 这篇 2013 年的文章):
(更现代的系统还可以在层级的顶部下方有 另一层)。
在 x86 系统上, base-page (基本页)大小为 4KB。第一级 huge pages (巨页)是通过标记 page middle directory (PMD, 页面中间目录)层级,即位于最底部的 page-table entry (PTE, 页表条目)级别之上的那一层中的条目来实现的。然后, PMD 条目直接引用覆盖地址空间较大部分的页面,而不是 PTE 表。这导致页面大小为 2MB;这些页面通常被称为 "PMD huge pages (PMD 巨页)"。向上移动到下一级目录 page upper directory (PUD) (页面上级目录)产生 1GB 的 huge pages (巨页)。
以这种方式实现的 huge pages (巨页)具有一些显著的优势。单个 PMD huge page (PMD 巨页)取代了 512 个 base pages (基本页),显著降低了内存管理的开销以及系统必须处理的 page faults (缺页中断)的数量。它还释放了 translation lookaside buffer (TLB) 中的多达 512 个条目, TLB 是一种稀缺的处理器资源,通常可能是性能瓶颈。因此,使用 huge pages (巨页)的程序可以运行得更快。最初,使用 huge pages (巨页)需要开发者和系统管理员付出特别的努力; 2011 年 transparent huge pages (THPs) (透明巨页)的添加使内核能够在后台将进程的 base pages (基本页) coalesce (合并)成 huge pages (巨页),从而使整个系统都可以获得 huge pages (巨页)的性能优势。
不幸的是, huge pages (巨页)并非完全没有代价。使用 huge-page (巨页)的代价可能是 internal fragmentation (内部碎片);如果进程仅使用 huge page (巨页)的一部分,则其余部分将被浪费。因此,启用 transparent huge pages (透明巨页)可能会显著增加 workload (工作负载)的内存使用量;如果系统在没有 huge pages (巨页)的情况下接近其限制,则启用它们可能会使其超出限制。因此,发行商通常会禁用此功能,并且人们很容易在网上找到建议保持这种状态的建议。
The multi-size option (多尺寸选项)
Multi-size THPs (多尺寸 THP)正如其名称所示,是将 huge-page (巨页)的概念扩展到更多尺寸,即那些与 page-table hierarchy (页表层级)不对应的尺寸。 mTHP 概念是由 transition to folios (转换为页) 启用的,以此作为在内核中管理内存的一种方式;现在可以表示任意(尽管是 2 的幂)大小的物理上连续的页面组。使用 mTHP 将再次减少内存管理开销,因为它们可以作为一个单元而不是作为独立的 base pages (基本页)进行管理。更新的 CPU 添加了 TLB-coalescing (TLB 合并)功能,该功能允许一个 8 页(x86)或 16 页(Arm)的 mTHP 由单个 TLB 条目表示,从而提高了 mTHP 可以带来的性能优势。
因此,使用 mTHP 可以带来与传统 huge pages (巨页)相似的性能提升,但 internal-fragmentation (内部碎片)成本更低。对于内核而言,分配 mTHP 也比分配完整的 PMD 或 PUD huge page (巨页)容易得多,尤其是在系统已经运行一段时间并且内存已变得碎片化之后。因此,近年来,人们为提高内核使用 mTHP 的能力付出了相当多的努力,这并不奇怪。
THP 页面机制可以通过几种方式将 huge pages (巨页)注入到进程的地址空间中。其中一种是在最初分配页面时。例如,内核可以分配一个 huge page (巨页)并在单个操作中读取所有内容,而不是使用 base page (基本页)来满足 page fault (缺页中断)。另一种方法是在后台检查进程的地址空间,并识别可以替换为 huge pages (巨页)的虚拟连续页面的范围。当找到这样的范围时,将分配一个 huge page (巨页),将 base pages (基本页)复制到其中,然后在 huge page (巨页)替换页表中的它们之后释放。后一个任务由 khugepaged
内核线程执行,该线程受益于能够观察进程实际使用的内存页面。但是, khugepaged
线程只能创建 PMD huge pages (PMD 巨页);它早于 mTHP,并且不创建它们。有问题的两个补丁集都旨在填补这一空白。
在研究它们之前,还有一些与这两种方法相关的有用背景知识。有一个 sysctl knob (sysctl 旋钮) max_ptes_none
,它控制 khugepaged
可以分配多少新页面来创建 huge page (巨页)。例如,如果一个合适的地址空间范围包含 500 个 active pages (活动页面),则 khugepaged
必须分配 12 个额外的页面才能拥有组成 PMD huge page (PMD 巨页)的完整 512 个页面。如果 max_pte_none
设置为至少 12,则 khugepaged
将继续,否则它将放弃该 huge page (巨页)。在 khugepaged
中创建 mTHP 会增加必须对此 knob (旋钮)的设置进行思考的量。
Improving khugepaged
(改进 khugepaged)
来自 Nico Pache 的 第一个补丁集 更改了核心 khugepaged
扫描循环(在当前内核中称为 hpage_collapse_scan_pmd()
,尽管该系列将其重命名为 khugepaged_scan_pmd()
)。当前实现会立即停止扫描 candidate huge page (候选巨页),如果找到 max_pte_none
个 unmapped pages (未映射页面)。在修改后的版本中,它将扫描 huge page (巨页)的整个地址范围,并创建一个 bitmap (位图),标记可以在该页面中创建 mTHP 的位置。考虑的最小尺寸为 order 3 (8 个页面);将来可能会更改为允许 Arm 系统上的最小 order 4 ,与可以合并 TLB 条目的大小相匹配。
在确定是否可以将给定的 mTHP 大小的 chunk (块)转换为实际的 mTHP 时,该补丁会相应地缩放 max_pte_none
值。当查看一个 8 页的 chunk (块)时, max_pte_none
将除以 64 (512/8) 以确定必须实际存在多少页。
扫描 PMD 后, khugepaged
将首先考虑是否可以将整个内容转换为 PMD huge page (PMD 巨页)。如果失败,将查阅扫描期间创建的 bitmap (位图),以尝试创建尽可能大的 mTHP,具体取决于实际驻留在内存中的页面。处理完 bitmap (位图)并创建任何 mTHP 后, khugepaged
将继续扫描下一个 PMD。
这种方法存在一个问题,两个补丁集都必须解决;它被称为 "creep (蔓延)"。假设 max_pte_none
已设置为 511,这恰好是其默认值。在该设置下,即使是单个 resident page (常驻页面)也足以导致 khugepaged
填充 huge page (巨页)的其余部分,这很可能是用户想要的。但是,在处理 mTHP 时,情况会发生一些变化。扫描 PMD 时,每个单页都会导致创建 8 页的 mTHP,正如预期的那样。但是,下次扫描发生时,这些 mTHP 将各自转换为 16 页的 mTHP,因为每个 mTHP 都包含足够的页面以使 khugepaged
将它们提升到下一个大小。
随着时间的推移,这些 mTHP 都将 "creep (蔓延)" 到 PMD 大小,而这正是创建补丁集要避免的结果。为防止这种情况发生,管理员需要将 max_pte_none
设置为小于 PMD huge page (PMD 巨页)大小一半的值,即 255 或更小的值。
来自 Dev Jain 的 第二个补丁系列 采用了一种略有不同的方法。 khugepaged
首先像以前一样以 PMD 大小的 chunk (块)进行扫描,但是如果无法 collapse (合并)给定的 PMD 范围,它将下降到一个较小的 order (阶)并再次尝试。 实现此算法的代码 是一堆 goto
语句,有点难以理解,但其目的是以导致成功创建 mTHP 的大小扫描每个内存范围。完成 PMD 条目后, order (阶)将重置为最大值以启动下一个条目。
该系列对 creep (蔓延)问题采取了更严格的方法;仅当 max_ptes_none
设置为零或 511 时,它才会执行 mTHP collapse (合并)。在前一种情况下,仅当它将替换的所有 base pages (基本页)都已经存在时,才会创建 mTHP;在后一种情况下,明确允许 creep (蔓延)到完整的 PMD 大小。
Pache 发布了 另一个补丁集,该补丁集可以与任何一个khugepaged
mTHP 实现一起使用。在当前内核中,可以通过一组 sysctl knobs (sysctl 旋钮)显式启用或禁用各种大小的 mTHP(在Documentation/admin-guide/mm/transhuge.rst中有文档记录)。always
的设置会导致内核尝试创建该大小的 mTHP,而never
会禁用该大小。 Pache 添加了一个新的defer
设置,该设置将导致内核不使用给定大小来响应 page faults (缺页中断),但将允许khugepaged
collapse (合并)到该大小。此处的目的是将 mTHP 分配限制在已证明有用的内存中,希望在享受 mTHP 优势的同时进一步降低 mTHP 的使用成本。
截至撰写本文时,这两个补丁集都是新发布的;除了 Andrew Morton 请求 基准测试结果之外,尚未产生任何真正的审查讨论。因此,无法预测哪个(如果有的话)补丁集将被接受,或者在发生这种情况之前可能需要进行多少更改。但是,人们显然希望更好地利用 mTHP,因此这项工作背后的目标很容易理解;迟早,内核几乎肯定会获得某种在进程运行时在后台创建 mTHP 的能力。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~