在 OpenAtom openEuler(简称 "openEuler") 20.03 LTS 历史版本中,ARM64 架构下默认基础页大小配置为 64KB,相比传统的 4KB 基础页,在访存方面,64K 页面能带来更快的 page tale walk、更低的 TLB miss,在存储 IO 路径上也能提升读写效率,因此 64K 基础页能够提升数据库等部分场景下的性能。然而,64K 基础页却面临南北向生态问题(部分应用软件和外设驱动需要重新适配或重新编译)和内存底噪开销问题(64K 相比 4K 粒度更粗,内存利用率更低,部分场景会带来巨大的内存消耗)。为此,openEuler 在 22.03 版本将 ARM64 的默认基础页大小配置改回了 4K,在性能上 64K 基础页有较大优势的场景,则可以单独编译 64K 基础页的二进制版本来支持,或者采用大页技术,虽然传统大页技术同样能够提升访存性能,但是它面临着使用不够灵活(hugetlb)和性能 QoS 无法保证(THP)的问题。
ARM64 内存地址翻译对比
那么有没有一种技术,可以在一个内核二进制版本中,实现 4K 基础页的南北向生态兼容,以及 64K 页的性能提升,同时应用能够无感使用,并能够有效控制内存底噪?openEuler 24.03 版本带来的动态复合页(large folio)技术正在解决这个难题。
动态复合页技术架构介绍
长期以来 Linux 内核中物理内存是基于 struct page 来管理的,每个 page 对象描述一个基础页(如 4K),随着当前大模型、大数据等业务大内存需求,单个系统上的内存容量可以达到 TB 级别,以 page 为单位管理内存越显低效。folio(拉丁语 foliō,对开本)最早是 Oracle 的一名工程师提出的,背景是在搭载 6TB 内存的服务器上,page 结构体达到了 15 亿个,开销巨大,同时内核内存回收模块的 LRU 链表长度将达到上亿,导致 LRU lock 锁竞争更加严重,持有该 spinlock 锁期间由于遍历超长链表 cache miss 也会更高,极其低效。Linux 内存管理基于 page(页)转换到由 folio 进行管理,相比 page,folio 可以由一个或多个 page 组成,采用 struct folio 参数的函数声明,将对整个 “页面” 进行操作,它可以是单个 page,也可以是多个 page(large folio)。采用 large folio 后,可以提升 LRU 管理效率、减少 page fault 次数提高内存分配效率、以 large folio 粒度建立大页映射可以降低 page table walk 开销,以及降低 TLB miss。
openEuler 24.03 版本基于 linux 6.6 LTS 版本作为基线进行演进,linux 6.6 基线版本已经完成了部分模块对基础 folio(4K)的重构,包括:内存分配、内存回收、pagecache、部分文件系统(xfs 等),而对于 large folio 的支持则普遍缺失。为此,华为内核团队除了在 linux 社区中积极贡献 folio 相关补丁的同时,也在 openEuler 24.03 配套的 6.6 内核中实现了内存管理子系统和文件系统对于 large folio 的大量支持和软硬协同优化,并提供灵活的控制策略,完善了动态复合页技术架构:
openEuler24.03 LTS 动态复合页技术架构
内存管理子系统在以下方面实现了 large folio,提升访存性能和内存管理效率:
1)内存分配:支持 mmap 申请虚拟地址空间范围(VMA)时自适应按多种粒度的 large folio 对齐,如 64K、2M,以便建立大页映射;支持应用透明的代码段大页,用户态免修改,支持 64K、2M 等多种粒度 large folio 以降低代码段 iTLB miss,提升程序代码段访问性能。支持多粒度的匿名大页(multi-size THP),从而降低匿名页 TLB miss,提升匿名页访问性能。为了加速大页面内存分配,降低伙伴系统 zone 锁冲突,支持 PCP(per-cpu 页面缓存)缓存 64K 粒度的 large folio(for ARM64 contiguous bit 大页)。
2)底层核心机制:fork、munmap、mlock、madvise 等系统调用功能实现支持 large folio 和批量化操作。完善了内存迁移、内存规整、NUMA balance、内存反向映射对 large folio 的支持,提升相关操作性能。内存交换(swap)支持 large folio 粒度的免拆分交换,提高内存交换效率,并针对 ARM64 架构完善 MTE 特性使能时的兼容支持。
3)软硬协同优化:伙伴系统、大页、透明大页的 large folio 优化,支持大页拆分到任意 order,内存聚合拆分、内存释放时批量化刷新 TLB,避免频繁刷新 TLB 带来的性能开销。针对 ARM64 支持基于 contiguous bit 技术的大页,如 64K “大页”,充分利用硬件 TLB “压缩” 机制降低 TLB miss,提升程序性能。
ARM64 各级大页示意图
文件系统在以下方面实现了 large folio 优化,大幅提升 IO 性能:
1)iomap 框架回写流程支持批量映射 block,充分发挥 large folio 的优势提升 IO 的回写性能:iomap 框架脏页回写流程以文件系统 block size 为粒度迭代分配文件系统 block 并组织 IO 下发,使能 large folio 后无需经过多轮迭代下发一个 dirty folio,通过优化支持以 folio 为粒度进行迭代,可显著减少文件系统空间分配次数。
2)支持 ext4 默认模式下 buffer 写批量预留 block,提升 IO 前台写性能:ext4 文件系统 buffer write 以 block size 为粒度预留文件系统空间,当用户下发大 IO 时需要多次迭代处理,无法批量为大 IO 做空间预留;扩展 buffer write 空间预留接口支持批量按需预留连续空间,可大幅减少迭代操作的次数,提升 IO 前台写入 page cache 的性能,大幅优化各类 benchmark 下 ext4 性能表现。
3)ext4 切换至 iomap 框架,并实现 ext4 支持 large folio,提升读写性能:ext4 文件系统 buffered io 读写流程以及 pagecache 回写流程弃用老旧的 buffer head 框架,切换到 iomap 框架可使 extent 分配按照用户下发 IO 大小迭代,预读和回写 IO 组装按 large folio 大小迭代,同时弃用 jbd2 data=ordered 的日志模式,文件数据无需依赖日志回写进程下发,大幅提升大 IO 读写性能。
Ext4 文件系统支持 large folio 后的数据 IO 下发流程
动态复合页通过多层级的策略控制,提供方便易用的系统级、容器级、进程级的 large folio 开关接口(注:其中容器级、进程级接口将在后续版本更新中提供),以及文件系统 large folio 开关接口,用户按需配置关键应用生效,非关键应用避免不必要的内存开销。
综上,动态复合页技术通过优化内存管理和文件系统两大核心子系统对于大页的自适应支持,应用无需修改,即可以减少 page table walk 和 TLB miss 从而提升访存性能,在 OS 内核层面可以做到更多的批量化优化,从而提升内存管理、IO 等关键路径上的性能。在底噪控制方面,提供多层级的控制开关,让关键应用按需使用。在兼容性上,操作系统的基础页面管理单元依然是 4K,可以保持原生的南北向生态兼容,同一个二进制版本即可兼顾性能和兼容性。
效果分析
大数据收益测试
打开动态复合页后,测试 hibench(Spark)结果各项指标平均提升 10%
Kafka 收益测试
打开动态复合页后,测试 kafka benchmark 结果:producer 带宽提升 26%,consumer 带宽提升 11%
MySQL 收益测试
打开动态复合页后,测试 MySQL benchmark(sysbench),受益于代码段可以用户态无感得合成 2M 大页从而使 iTLB miss 率最高降低 10 倍,sysbench 整体结果性能提升 3%+
IO 基础性能 benchmark 收益测试
打开动态复合页后,测试 FIO 读性能平均提升 59%,写性能平均提升 239%
内存分配 benchmark 收益测试
打开动态复合页后,测试 linux 社区常用的 benchmark 工具 will-it-scale 测试内存分配性能,受益于单次 pagefault 可批量分配 large folio 从而减少 page fault 次数,以及 PCP 缓存池的分配加速,匿名页和共享文件页的 page fault ops 平均提升 100%
后续规划
未来动态复合页技术将持续聚焦于数据中心常见场景的性能优化,并将与芯片结合充分利用 TLB 单元以覆盖更大内存,进一步降低 TLB miss 提升性能。同时也将把相关特性持续贡献到 Linux 社区。