3. 标准页与巨页:4KB页与2MB/1GB页的对比、页表层级差异

在 x86-64 架构下,操作系统默认使用 4KB 标准页 管理内存。对于 DPDK 这类需要大量连续物理内存、频繁进行 TLB 查找的高性能应用,4KB 页会带来严重的性能瓶颈。本章深入对比 4KB 页与 2MB/1GB 巨页在页表层级、TLB 覆盖范围、内存分配开销上的本质差异。

3.1 页表层级:从 4 级到 2 级

x86-64 的分页机制采用多级页表结构。页表层级数直接决定了虚拟地址到物理地址的转换路径长度,以及单次 TLB miss 后遍历内存的代价。

核心差异: 4KB 页需要 4 级页表(PML4 → PDPT → PD → PT),而 2MB 巨页仅需 3 级(PML4 → PDPT → PD),1GB 巨页仅需 2 级(PML4 → PDPT)。层级越少,TLB miss 后的硬件页表遍历(page walk)次数越少,延迟越低。
标准页与巨页页表层级对比 4KB / 2MB / 1GB 页表层级对比 4KB 标准页(4 级) PML4 PDPT PD PT 4KB 物理页 4 次内存访问 / 缺页 2MB 巨页(3 级) PML4 PDPT PD 2MB 物理页 3 次内存访问 / 缺页 1GB 巨页(2 级) PML4 PDPT 1GB 物理页 2 次内存访问 / 缺页

3.2 关键指标对比表

对比维度 4KB 标准页 2MB 巨页 1GB 巨页
页表层级 4 级(PML4→PDPT→PD→PT) 3 级(PML4→PDPT→PD) 2 级(PML4→PDPT)
单页大小 4 KB 2 MB(512 × 4KB) 1 GB(512 × 2MB)
TLB 覆盖范围(典型 64 项 L1 DTLB) 64 × 4KB = 256 KB 64 × 2MB = 128 MB 64 × 1GB = 64 GB
缺页中断次数(映射 1GB 内存) 262,144 次 512 次 1 次
页表占用(映射 1GB 内存) 约 4 MB 约 8 KB 约 4 KB
TLB miss 惩罚(page walk 次数) 4 次内存访问 3 次内存访问 2 次内存访问
适用场景 通用进程、小内存分配 DPDK 数据面、数据库、HPC 虚拟化、大内存数据库、GPU 显存映射
TLB 覆盖范围实战意义: DPDK 通常为每个 NUMA 节点预留 1~4 GB 巨页内存。使用 2MB 巨页时,仅需 8~32 个 TLB 条目即可覆盖全部数据面内存,TLB miss 率趋近于零。而使用 4KB 页则需要 256K~1M 个条目,远超 CPU TLB 容量,导致频繁 miss。

3.3 页表结构详解:4KB 页的 4 级转换

以 4KB 标准页为例,虚拟地址被划分为 5 个部分:PML4 索引(9 bit)、PDPT 索引(9 bit)、PD 索引(9 bit)、PT 索引(9 bit)、页内偏移(12 bit)。每次地址转换需要依次读取 4 个页表项,每个页表项 8 字节,共 32 字节内存访问。

4KB 页虚拟地址分解与页表遍历 4KB 标准页:虚拟地址分解与页表遍历 PML4 (9 bit) PDPT (9 bit) PD (9 bit) PT (9 bit) 偏移 (12 bit) 虚拟地址(48 bit) 硬件 page walk 流程 ① 读 PML4 表项 ② 读 PDPT 表项 ③ 读 PD 表项 ④ 读 PT 表项 ⑤ 获取物理页基址 + 偏移 → 物理地址 4 次内存访问 + 1 次组合 → 约 40~80 ns 延迟(未命中 TLB 时)

3.4 巨页的页表压缩原理

巨页通过 跳过中间页表层级 来压缩页表深度。以 2MB 巨页为例:

  • PD 页表项中的 PS 位(Page Size,bit 7) 被置为 1,表示该项直接指向 2MB 物理页,不再需要 PT 级。
  • 虚拟地址中原本用于 PT 索引的 9 bit 和页内偏移的 12 bit 合并为 21 bit 偏移(2MB = 221)。
  • 页表项数量从 4KB 页的 512 个 PT 项 + 1 个 PD 项,减少为仅 1 个 PD 项。
注意事项: 巨页虽然减少了页表层级和 TLB miss,但分配粒度变大。1GB 巨页的最小分配单位是 1GB,不适合小内存场景。DPDK 通常使用 2MB 巨页作为默认配置,仅在需要超大连续内存(如虚拟化 vhost 后端)时启用 1GB 巨页。

3.5 TLB miss 代价量化对比

假设一次内存访问延迟为 10 ns(L3 cache 命中),TLB miss 后的 page walk 需要逐级读取内存中的页表项:

页大小 page walk 次数 理论延迟(L3 命中) 实际延迟(内存访问) DPDK 数据面影响
4KB 4 次 4 × 10 ns = 40 ns 4 × 100 ns = 400 ns 每百万包增加 400 ms 延迟
2MB 3 次 3 × 10 ns = 30 ns 3 × 100 ns = 300 ns 每百万包增加 300 ms 延迟
1GB 2 次 2 × 10 ns = 20 ns 2 × 100 ns = 200 ns 每百万包增加 200 ms 延迟
实战结论: 在 DPDK 轮询模式(run-to-completion)下,每收到一个数据包都需要访问 mbuf 和 packet buffer。如果使用 4KB 页且 TLB 频繁 miss,每百万包将额外增加 400 ms 的地址转换延迟。对于 10 Gbps 线速(约 14.88 Mpps),这意味着每秒增加约 6 秒的纯地址转换开销。切换到 2MB 巨页后,TLB 覆盖范围扩大 512 倍,miss 率趋近于零,这部分开销几乎完全消除。

3.6 巨页配置对 DPDK 的直接影响

DPDK 在初始化时通过 rte_eal_init() 调用 rte_memseg_contig_walk() 遍历系统巨页。巨页大小直接影响以下关键参数:

  • mempool 创建: DPDK 的 mempool 需要从巨页中分配连续物理内存。2MB 巨页可以满足大多数 mempool 需求(通常 1~64 MB),1GB 巨页用于超大 mempool(如 512 MB 以上)。
  • Hugepage 文件系统: /dev/hugepages 挂载点下的文件大小等于巨页大小。DPDK 通过 mmap 映射这些文件获取物理地址。
  • iommu 映射: 使用 VFIO 驱动时,IOMMU 页表同样受益于巨页。2MB 巨页可减少 IOMMU 页表项数量,降低 DMA 映射开销。
# 查看系统支持的巨页大小
grep Hugepagesize /proc/meminfo

# 查看当前巨页分配情况
cat /proc/meminfo | grep -i huge

# DPDK 启动时指定巨页大小(示例:使用 2MB 巨页)
./dpdk-testpmd -l 0-3 -n 4 --socket-mem 1024,1024 \
  --huge-dir /dev/hugepages2M \
  --file-prefix pg2M \
  -- -i
最佳实践: 对于 DPDK 数据面应用,优先使用 2MB 巨页。它平衡了 TLB 覆盖范围(128 MB 以上)和分配灵活性(最小 2MB)。仅在需要映射超过 1GB 连续物理内存(如 GPU 显存、NVMe 大块 IO)时,才考虑 1GB 巨页。4KB 标准页应完全避免用于数据面内存。