大页不省内存,省 TLB。1 GB 内存用 4 KB 页要 26 万项页表,用 2 MB 页只要 512 项;512 项的 TLB 装小页只盖得住 2 MB,装大页能盖住 1 GB。
5.16 靠 TLB 兜住了多级页表的开销,这一节反过来:如果 TLB 装不下,就【把页做大】。
一、页数差多少
1 GB 内存:4 KB 页要 262144 页,2 MB 页只要 512 页。 页表项数也就跟着差 512 倍。 页表小了,建表快、换出快、缺页次数也少——但这不是主要收益。 主要收益在 TLB 上。
二、TLB 只有几百项,装不下小页
一个典型 TLB 有 512 项。 用 4 KB 页:全装满了也只能盖住 512 × 4 KB = 2 MB。访问 1 GB 的随机位置,TLB 几乎次次不命中。 用 2 MB 页:512 项能盖住 1 GB——整个 1 GB 一次装下。 实测:随机访问 1 GB,小页命中率 0.3%,大页 97.4%。 这个差距比【页表小了 512 倍】重要得多——因为 TLB 不命中意味着要查四级页表,那是四次额外访存。
三、代价
一、内部碎片:一页 2 MB,你只用 100 KB 就浪费 1.9 MB。 二、要有连续的物理内存:2 MB 连续在跑久了之后不容易凑出来——这正是 5.3 讲的外部碎片。 三、申请会失败:内存碎片多的时候,大页申请直接返回失败,所以程序得准备降级方案。 所以大页不是默认可用的:要么显式申请(mmap 加标志、或 hugetlbfs),要么靠【透明大页】自动合并。
四、透明大页与显式大页
透明大页(THP):内核自己判断,觉得合适就悄悄把 4 KB 页合并成 2 MB 页。不用改程序,默认通常开着。 显式大页(hugetlbfs):程序自己申请,预先分配好,一定用得上。数据库、虚拟化常用。 THP 的争议:它合并的时候要花时间,且【可能合并不该合并的】,所以有些服务会显式关掉它。 一句话:大页适合【大块、顺序、长期驻留】的内存,不适合【小而零碎】的分配。
五、动手 Demo:标准大页——把 4KB 的页换成 2MB 的页
这一节算三笔账:页数、TLB 覆盖范围,再用一个模拟 TLB 真跑随机访问,看命中率差多少。
total, small, huge = 1024 ** 3, 4096, 2 * 1024 ** 2
ns, nh = total // small, total // huge
print("4KB 页", ns, "个")
print("2MB 页", nh, "个")
tlb = 512
print("TLB 装 4KB 页能盖", tlb * small // 1024 // 1024, "MB")
print("TLB 装 2MB 页能盖", tlb * huge // 1024 ** 3, "GB")
实跑输出:
--------------------------------------------------------------------
标准大页——把 4KB 的页换成 2MB 的页
--------------------------------------------------------------------
① 1 GB 内存,页数差多少
页大小 要多少页 页表项
-----------------------------------
4 KB 262144 页 262144 项
2 MB 512 页 512 项
**少 512 倍。**
② TLB 只有 512 项,能覆盖多少内存
页大小 TLB 全命中时覆盖 1 GB 要缺多少次 TLB
----------------------------------------------------
4 KB 2 MB 512 次
2 MB 1.00 GB 0 次
4 KB 页时,**TLB 全部装满也只盖得住 2 MB**,连 1 GB 的零头都不够。
2 MB 页时,**512 项就能盖住 1.00 GB**——整个 1 GB 一次装下。
③ 真跑一次:随机访问 1 GB,看 TLB 命中率
页大小 随机访问次数 TLB 命中 命中率
-------------------------------------------------
4 KB 页 20000 51 0.3%
2 MB 页 20000 19488 97.4%
**同样是随机访问,小页几乎次次不命中,大页次次命中。**
**这就是大页的全部理由:不是省内存,是省 TLB。**
④ 代价
好处 代价
--------------------------------------------------
TLB 覆盖大,命中率高 一页 2 MB,用不完就浪费
页表项少,建表快 要有连续 2 MB 的物理内存
缺页次数少 内存碎片多时申请会失败
**所以大页不是默认可用的**:要显式申请(`mmap` 加标志、或 hugetlbfs),
**系统只把【透明大页】这种自动版默认打开**,专门给大块顺序访问的程序用。
页数差 512 倍,TLB 覆盖率也差 512 倍。 0.3% 对 97.4%——这就是为什么大页在某些负载下能带来百分之几十的性能提升。 注意这个模拟只算了 TLB 容量,没算查表开销;真实系统里 TLB 不命中要查四级页表,代价更大。 所以【大页的收益主要落在【随机访问大块内存】的场景】——虚拟机内存、数据库缓冲池、大数组计算。 对【小而零碎的分配】用大页,只会浪费内存。
📋 Linux 上看大页配了多少:grep -i huge /proc/meminfo——HugePages_Total 与 Hugepagesize 两行就够了。
30 秒实验
grep -i huge /proc/meminfo,看透明大页与显式大页各配了多少。cat /sys/kernel/mm/transparent_hugepage/enabled,看 THP 是 always、madvise 还是 never。grep -o 'pdpe1gb' /proc/cpuinfo | head -1,看 CPU 支不支持 1 GB 大页。
常见误区
- 【大页是为了省内存】。它反而浪费内存(内部碎片);它省的是 TLB 项。
- 【页越大越好】。大页要连续的物理内存,碎的时候申请不到;小分配用大页纯属浪费。
- 【透明大页应该永远开着】。它合并要花时间,有些延迟敏感的服务会显式关掉。
自测三问(点开对答案)
1. 大页最主要的好处是什么? —— **省 TLB 项**:同样的 TLB 容量能覆盖大得多的内存范围,**命中率从个位数升到九十几。** 2. 大页的三个代价是什么? —— **内部碎片、要连续物理内存、碎片多时申请失败**。 3. 透明大页和显式大页差在哪? —— **THP 是内核自动合并,不用改程序**;**显式大页要程序自己申请、预先分配,确定用得上。**- ☐ 第六章 6.1 高速缓存
相关笔记
- 多级页表 — 需要 TLB 兜底
- 简单的内存分配 — 为什么凑不出连续大块
- 转译后备缓冲区 — TLB 本身
- 🐧 Linux是怎样工作的 · 索引