地址翻译也要缓存,那张缓存叫 TLB。它只有几百项,512 项 × 4 KB 只能盖住 2 MB;换成 2 MB 的大页,同样的 TLB 能盖住 1 GB——随机访问 64 MB 的命中率从 3.5% 跳到 99.8%。
5.5 讲了页表,5.16 讲了四级页表,这一节补上那块让它们能用的缓存——没有 TLB,四级页表根本不可用。
一、为什么翻译也要缓存
四级页表意味着一次访存要先查四次表,再取一次数据——慢五倍。 但 TLB 命中时,翻译结果是现成的,一次都不用查。 所以真实情况是:95% 以上的访问 TLB 命中,只有少数要真去查表。 平均下来每次访存多 0.2 次查表访存——基本可以忽略。 注意这 0.2 次不是【白给】的:它是靠【局部性】换来的,程序总在一小片地址里来回走,TLB 才装得下。
二、TLB 只有几百项
典型 TLB 是 512 项左右。 用 4 KB 页时,512 × 4 KB = 2 MB——这就是它盖得住的最大工作集。 工作集 64 MB 就要 16384 页,是 TLB 的 32 倍——盖不住。 工作集 4 GB 要 100 多万页——更别提了。 所以 TLB 的命中率完全取决于【程序有没有局部性】,跟 TLB 大小关系没那么大。
三、大页是救 TLB 的办法
同一个 64 MB 的工作集:4 KB 页要 16384 项,2 MB 页只要 32 项。 实测 TLB 命中率:4 KB 页 3.5%,2 MB 页 99.8%。 差了两个数量级。这就是 5.17 说的【大页不省内存、省 TLB】在这里的兑现。 所以虚拟化、数据库缓冲池、大数组计算都偏爱大页——它们的工作集天生很大,靠小页根本盖不住。 代价照旧:内部碎片、要连续物理内存、碎片多时申请失败。
四、两件事别混
TLB 缓存的是【地址翻译】;数据缓存(L1/L2/L3)缓存的是【数据本身】。 两者独立:TLB 命中但数据不在缓存里,照样要等内存。 所以一次访存可能同时缺两样:翻译没缓存 + 数据没缓存——那是最坏情况,等于把两次慢都付一遍。 排查时分开看:TLB 缺失多说明【地址跳得散】,数据缺失多说明【数据跳得散】。
五、动手 Demo:转译后备缓冲区——翻译也要缓存
这一节算三笔账:TLB 缺失要额外查几次表、512 项能盖多大工作集、换成大页之后命中率能救回多少。
th, tlb = 0.95, 512
print("平均额外访存", (1 - th) * 4)
for size in (64 * KB, 2 * MB, 64 * MB, 4 * GB):
print(size, size // 4096, "页")
# 用 LRU 模拟 TLB,比较 4 KB 页与 2 MB 页
for ps in (4096, 2 * MB):
npg = 64 * MB // ps
hits = tlb_sim(npg, tlb, 20000)
print(ps, hits / 20000.0)
实跑输出:
--------------------------------------------------------------------
转译后备缓冲区——翻译也要缓存
--------------------------------------------------------------------
① 一次访存,可能要先查几次页表
假设 TLB 命中率 95%、四级页表:
情况 额外访存次数 占多少
-------------------------------------------
TLB 命中 0 次 95%
TLB 未命中 4 次(每级一次) 5%
**平均每次访存多 0.20 次查表访存。**
**听起来不多,但把这 5% 的未命中摊到每一次内存访问上,就不是小数了。**
② TLB 只有几百项,工作集一大就不够
工作集 要用多少页 TLB 装得下吗
-----------------------------------------------
64 KB 16 页 装得下
2 MB 512 页 装得下
64 MB 16384 页 装不下(缺 33 倍)
4 GB 1048576 页 装不下(缺 2049 倍)
**TLB 只有 512 项,所以它盖得住的最大工作集是 512 × 4 KB = 2 MB。**
**超过这个数,就靠【局部性】救命**——**程序总在一小块地方来回走,TLB 就够用。**
③ 为什么大页能救 TLB
页大小 512 项盖得住 随机访问 64 MB 的命中率
----------------------------------------------------
4 KB 2.0 MB 3.5%
2 MB 1.0 GB 99.8%
**同样是 64 MB 的工作集,4 KB 页的 TLB 命中率是个位数,2 MB 页接近满。**
**所以【大页】不是省内存,是让 TLB 重新有用**(5.17 那节的结论在这里兑现)。
5% 的缺失、每次缺 4 次查表——平均多 0.2 次访存,可以接受。 512 项 × 4 KB = 2 MB,所以 64 MB 的工作集是 TLB 的 32 倍。 同样的 TLB,4 KB 页命中 3.5%,2 MB 页命中 99.8%。 所以【大页】不是锦上添花,是【工作集大的程序唯一能用 TLB 的办法】。 这也解释了为什么【同样的程序,开了透明大页会快一截】——快的部分就是省下的查表。
📋 Linux 上看 TLB 效果:perf stat -e dTLB-loads,dTLB-load-misses <命令>——两个数一除就是 TLB 命中率。
30 秒实验
perf stat -e dTLB-load-misses,dTLB-loads ls,算一次 TLB 命中率。cat /sys/kernel/mm/transparent_hugepage/enabled,看 THP 开没开。- 把上例的工作集从 64 MB 改成 4 MB,看 4 KB 页的命中率变多少(应该接近满)。
常见误区
- 【TLB 和数据缓存是一回事】。TLB 缓存翻译结果,数据缓存缓存数据,两者独立。
- 【加大 TLB 就能解决】。TLB 加不了太多(要快到 1 周期);真正的解法是【大页】和【局部性】。
- 【TLB 缺失会报错】。它只是多查几次表,慢一点,不影响正确性。
自测三问(点开对答案)
1. 为什么地址翻译也要缓存? —— **四级页表意味着一次访存要先查四次表**,慢五倍;**TLB 命中就一次都不用查。** 2. 512 项的 TLB 用 4 KB 页能盖多大?用 2 MB 页呢? —— **4 KB 页:2 MB;2 MB 页:1 GB。**差 512 倍,**所以大页是工作集大的程序唯一能用 TLB 的办法。** 3. TLB 缓存与数据缓存有什么区别? —— **TLB 缓存【地址翻译结果】,数据缓存缓存【数据本身】。****两样都缺的时候,等于把两次慢都付一遍。**- ☐ 6.8 页面缓存
相关笔记
- 页表 — 被缓存的那些表
- 标准大页 — 省 TLB 的办法
- 页面缓存 — 另一个缓存,缓存的是文件
- 🐧 Linux是怎样工作的 · 索引