大页内存与透明大页详解
大页内存与透明大页详解
概述
现代 x86-64 CPU 的默认页大小是 4KB。一条 64GB 内存的服务器上有 16,777,216 个物理页,但 TLB(Translation Lookaside Buffer)只有约 1536 个条目。当工作集超过 TLB 覆盖范围(4KB × 1536 ≈ 6MB)时,每次内存访问都可能触发 page table walk,增加 1-5 次额外的内存访问。大页(HugePages)通过将页放大到 2MB 或 1GB,把 TLB 覆盖范围扩大 512-262144 倍。
一句话:4KB 页 → TLB 覆盖 ~6MB。2MB 页 → TLB 覆盖 ~3GB。1GB 页 → TLB 覆盖 ~1.5TB。
TLB 为什么重要
一次内存访问的代价
虚拟地址 → TLB 查询
↓ 命中(~0.5-1 cycle)
直接得到物理地址 → 访问内存(~100ns)
↓ 未命中(TLB miss)
四级页表遍历(4 × 内存访问)
PGD → PUD → PMD → PTE (~400-500ns 额外开销)
然后 → 访问内存(~100ns)
每次 TLB miss 的代价是 命中时的 5 倍以上。工作集越大,miss 越多。大页直接减少页表级数(2MB 页只用 3 级,1GB 页只用 2 级),大幅降低 miss 率。
TLB 规格参考
| CPU | L1 DTLB | L2 STLB |
|---|---|---|
| Intel Skylake | 64 × 4KB + 32 × 2MB/4MB | 1536 × 4KB + 相同 2MB/4MB |
| Intel Sapphire Rapids | 96 × 4KB + 48 × 2MB/4MB | 2048 × 4KB |
| AMD EPYC Genoa | 72 × 4KB + 72 × 2MB | 3072 × 和 L1 共享格式 |
注:大页条目数较少但每条覆盖的内存大得多。关键不是条目数,是覆盖总量。
显式大页(HugePages)
hugetlbfs 机制
显式大页不是自动的:系统预留一块连续物理内存作为大页池,应用通过 mmap hugetlbfs 文件系统申请。
# 1. 预留 2MB 大页
echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
# 预留 1024 × 2MB = 2GB
# 2. 查看预留情况
cat /proc/meminfo | grep -i huge
# HugePages_Total: 1024
# HugePages_Free: 1024
# HugePages_Rsvd: 0
# HugePages_Surp: 0
# Hugepagesize: 2048 kB
# Hugetlb: 2097152 kB ← 2GB 总容量
# 3. 持久化配置
echo "vm.nr_hugepages = 1024" >> /etc/sysctl.d/99-hugepages.conf
sysctl -p /etc/sysctl.d/99-hugepages.conf
# 4. 应用程序使用(hugetlbfs mount)
mkdir -p /mnt/huge
mount -t hugetlbfs -o pagesize=2M none /mnt/huge
# 应用通过 mmap /mnt/huge 获取大页
1GB 大页
GPU 训练场景下,1GB 大页可以大幅减少 driver 和 NCCL 通信中的 TLB miss:
# 配置 16 个 1GB 大页
echo 16 > /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages
# 持久化(grub)
# GRUB_CMDLINE_LINUX="... hugepagesz=1G hugepages=16 default_hugepagesz=1G"
# 验证
cat /proc/meminfo | grep Huge
# HugePages_Total: 16
# Hugepagesize: 1048576 kB
限制:1GB 大页需要在引导阶段分配(无法动态增减),且必须找到连续的 1GB 物理内存。长时间运行后内存碎片化会分配失败。
K8s 中的 HugePages
apiVersion: v1
kind: Pod
spec:
containers:
- name: db
image: postgres:16
resources:
limits:
hugepages-2Mi: 256Mi # 128 个 2MB 页
memory: 8Gi
requests:
hugepages-2Mi: 256Mi
volumeMounts:
- name: hugepages
mountPath: /dev/hugepages
volumes:
- name: hugepages
emptyDir:
medium: HugePages-2Mi
K8s 自动在节点上为 Pod 预留大页,调度时确保目标节点有足够的空闲大页。
透明大页(THP)
THP 工作机制
THP 是内核自动管理的:后台线程 khugepaged 持续扫描内存,发现连续的 4KB 页时自动合并为 2MB 大页。用户应用无需任何代码修改。
khugepaged 线程:
→ 扫描匿名页
→ 发现 512 个连续的 4KB 页(对齐到 2MB 边界)
→ 将它们合并为一个 2MB 大页
→ 更新页表,减少 TLB miss
三个模式
| 模式 | /sys/kernel/mm/transparent_hugepage/enabled | 行为 |
|---|---|---|
| always | [always] madvise never | 内核积极合并所有符合条件的匿名页 |
| madvise | always [madvise] never | 只有应用通过 madvise(MADV_HUGEPAGE) 请求时才合并 |
| never | always madvise [never] | 完全不使用 THP |
THP 的开销:compaction
THP 需要找到 2MB 连续物理内存。如果内存碎片化,khugepaged 会触发 compaction(内存压缩/整理),这会:
- 消耗 CPU(
khugepaged和kcompactd线程) - 暂停应用的内存分配(direct compaction)
- 增加延迟抖动(p99 延迟井喷)
这就是 etcd、Redis 等延迟敏感应用必须关闭 THP 的原因。
# 检查 compaction 开销
cat /proc/vmstat | grep compact
# compact_migrate_scanned, compact_free_scanned 持续增长 = 碎片化严重
# 检查 khugepaged CPU 使用
top -p $(pgrep khugepaged)
THP 决策树
需要极低延迟(< 1ms p99)?
├── 是 → 关闭 THP,用显式 HugePages
│ 场景:etcd、Redis、Kafka、金融交易
└── 否
├── 长时间运行的大内存应用?
│ └── 是 → madvise(应用主动申请)
│ 场景:PostgreSQL、MySQL、JVM
└── 短生命周期、频繁 fork?
└── 是 → madvise 或 never
场景:PHP-FPM、短 job
关闭 THP 的多种方式
# 方法 1:运行时关闭
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
# 方法 2:grub 启动参数(永久)
# /etc/default/grub
GRUB_CMDLINE_LINUX="... transparent_hugepage=never"
# 方法 3:systemd 服务(确保在应用启动前关闭)
# /etc/systemd/system/disable-thp.service
[Unit]
Description=Disable THP
Before=etcd.service kubelet.service
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/enabled && echo never > /sys/kernel/mm/transparent_hugepage/defrag'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
THP 与 K8s 的兼容性问题
Redis / etcd 的 THP 陷阱
# Redis 启动日志:
# WARNING you have Transparent Huge Pages (THP) support enabled in your kernel.
# This will create latency and memory usage issues with Redis.
# etcd 官方文档:
# "etcd 强烈建议在运行环境中关闭透明大页"
原因是 fork() 的 COW(Copy-on-Write):大页的 COW 粒度是 2MB 而非 4KB。Redis BGSAVE 或 etcd 快照时 fork(),即使只修改了子进程的 1 字节,也会复制整个 2MB 大页,导致:
- 内存使用暴增
fork()耗时大幅增加(阻塞主进程)
验证与监控
# 检查是否生效
cat /sys/kernel/mm/transparent_hugepage/enabled
# 期望:[never]
# 在容器内检查(取决于挂载方式)
# K8s 1.29+ 默认会挂载 hostPath /sys 到容器
# Prometheus 监控指标
node_memory_HugePages_Free
node_memory_HugePages_Rsvd
node_memory_HugePages_Total
rate(node_vmstat_compact_migrate_scanned[5m])
HugePages 在 GPU 训练中的应用
为什么 GPU 训练需要大页
GPU 训练的典型内存访问模式:
- CUDA driver + nvidia kernel module 需要 pin 住大段主机内存做 DMA(GPU Direct)
- NCCL 通信缓冲区(multi-GB)如果不使用大页,TLB miss 频繁
- 数据加载(DataLoader)的 pinned memory
# 大模型训练节点推荐设置(8×A100/H100,512GB RAM)
echo 131072 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages # 256GB 2MB 大页
echo 8 > /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages # 8GB 1GB 大页
# 或 grub 一次性设
# GRUB_CMDLINE_LINUX="... hugepagesz=1G hugepages=8 hugepagesz=2M hugepages=131072 default_hugepagesz=2M"
性能收益参考
| 场景 | 默认(4KB) | 2MB 大页 | 1GB 大页 |
|---|---|---|---|
| NCCL AllReduce (8×A100, 128MB buffer) | 基准 | +3-5% | +5-8% |
| CUDA unified memory 迁移延迟 | 基准 | -20% | -35% |
| 数据库 TPS(PostgreSQL) | 基准 | +10-15% | — |
| DPDK 包转发吞吐 | 基准 | — | +15-25% |
常见问题
| 问题 | 原因 | 解决 |
|---|---|---|
echo 1024 > nr_hugepages 返回错误 | 没有 1024×2MB 连续物理内存 | 在启动阶段预留,或降低数量 |
| THP 设为 never 后系统重启又变回 always | 未通过 grub 持久化 | 添加 transparent_hugepage=never 到 GRUB_CMDLINE_LINUX |
K8s Pod 报 hugepages-2Mi 不足 | 节点大页被其他 Pod 占用 | kubectl describe node 查看 hugepages-2Mi allocatable |
khugepaged CPU 100% | THP always + 内存碎片化,compaction 疯狂 | 切换为 madvise 或 never |
| Pod 迁移后大页”消失” | 大页是 node-level 资源,重启后恢复 | 确保节点启动脚本持久化了大页配置 |
关联知识
- NUMA 架构与亲和性调优 — 大页分配与 NUMA 节点亲和性
- cgroup v2 详解 — hugetlb 是 cgroup v2 的独立控制器
- 网络内核参数调优 — DPDK 需要 1GB HugePages
- CPU 隔离与中断亲和性 — khugepaged/kcompactd 应限制在非隔离 CPU
- ../k8s/特性详解/etcd 运维详解 — etcd 生产环境必须关闭 THP
参考资源
- hugetlbpage 文档:https://www.kernel.org/doc/html/latest/admin-guide/mm/hugetlbpage.html
- THP 文档:https://www.kernel.org/doc/html/latest/admin-guide/mm/transhuge.html
- Redis THP 警告说明:https://redis.io/docs/latest/operate/oss_and_stack/management/optimization/latency/
- etcd 调优指南:https://etcd.io/docs/latest/tuning/
学习时间
| 阶段 | 时间 | 备注 |
|---|---|---|
| 深度梳理 | 2026-06-30 | TLB 原理、显式/透明大页对比、THP 陷阱、GPU 训练实践 |
状态: 🌱 学习中 下次复习日期: 2026-07-07