平均访问时间 = 命中开销 + 缺失率 × 缺失代价。命中率从 99% 掉到 95%,平均时间就从 6 个周期涨到 14 个周期——只掉 4 个点,性能掉一倍多。
6.1 到 6.5 讲了一堆数字,这一节把它们收成一个能算的公式,顺便说明为什么【多级】一定比【一级大缓存】强。
一、一层缓存怎么算
公式:平均访问时间 = 命中开销 + 缺失率 × 缺失代价。 代入:命中 4 周期、缺失 200 周期——命中率 99% 时是 6 个周期,95% 时是 14 个周期。 命中率掉 4 个点,平均时间涨了一倍多。 这就是为什么【缓存命中率】是性能里最敏感的一个数——它不线性,缺失那部分的代价被放大了 50 倍。 所以【优化缓存】的着力点是【减少缺失次数】,不是【让命中更快】。命中那 4 个周期已经压不动了。
二、多级怎么算
从最下面往上算:把上一层的缺失当成下一层的一次访问。 L3:40 + 10% × 200 = 60 周期。L2:12 + 10% × 60 = 18 周期。L1:4 + 10% × 18 = 5.8 周期。 结果只有 5.8 周期——比 L1 的 4 周期多一点,但比直接访问内存的 200 周期快几十倍。 这就是多级的全部意义:每一层只兜住上一层的 10%,层层下来,绝大多数访问都被最便宜的那一层接住了。
三、为什么不是【一级超大的缓存】
因为【快】与【大】在硬件上是矛盾的:要 1 纳秒,就得又小又贵。 一级大缓存只有两个选择:要么慢成 L2,要么贵到买不起。 分层是唯一能同时满足【快】和【大】的办法——用 90% 的命中率把慢的那层挡住。 这个思路不只用在缓存上:页表分级、存储金字塔、CDN,全是同一招。
四、三条结论
一、命中率比容量重要:掉 1 个点就可能掉十几个周期。 二、多级比一级大缓存有效:最常用的那部分才值得用最贵的存储。 三、局部性是一切的前提:没有局部性,命中率会掉到接近 0。 三条合起来:缓存不是【装得多】,是【装得准】。
五、动手 Demo:总结——平均访问时间怎么算
这一节把公式摆出来,算一遍【命中率每掉几个点,平均时间涨多少】,再算一遍多级缓存叠加之后的结果。
hit, miss = 4, 200
for rate in (0.99, 0.95, 0.90, 0.80, 0.50):
avg = hit + (1 - rate) * miss
print(rate, avg)
l3 = 40 + 0.1 * 200
l2 = 12 + 0.1 * l3
l1 = 4 + 0.1 * l2
print("L3/L2/L1", l3, l2, l1)
实跑输出:
--------------------------------------------------------------------
总结——平均访问时间怎么算
--------------------------------------------------------------------
① 一层缓存:平均时间 = 命中开销 + 缺失率 × 缺失代价
命中率 缺失率 平均访问时间 相对全命中
--------------------------------------------------
99% 1% 6.0 周期 1.5 倍
95% 5% 14.0 周期 3.5 倍
90% 10% 24.0 周期 6.0 倍
80% 20% 44.0 周期 11.0 倍
50% 50% 104.0 周期 26.0 倍
**命中率从 99% 掉到 95%,平均时间就从 6 个周期涨到 14 个周期**——
**只掉了 4 个点,性能掉了一倍多。**
**这就是为什么【缓存命中率】是性能里最敏感的一个数。**
② 两层缓存:把上一层的缺失当下一层的访问
层级 命中率 命中开销 这一层要承担
----------------------------------------------
L1 90% 4 周期 10% 落到 L2
L2 90% 12 周期 10% 落到 L3
L3 90% 40 周期 10% 落到内存
内存 — 200 周期 全部由它兜底
**从下往上算**:**L3 这一层平均 60.0 周期,L2 18.0 周期,L1 5.8 周期。**
**注意最后结果只有 5.8 周期**——**比 L1 的 4 周期多了一点,**
**但比直接访问内存的 200 周期快了几十倍。**
**这就是【多级缓存】的全部意义**:**每一层只兜住上一层的 10%,**
**层层下来,绝大多数访问都被最便宜的那一层接住了。**
③ 三层结论
结论 为什么
----------------------------------------------------------
缓存命中率比容量重要 掉 1 个点就可能掉十几个周期
多级比单级大缓存有效 最常用的那部分才值得用最贵的存储
局部性是一切的前提 没有局部性,命中率会掉到接近 0
命中率 99%→6.0 周期,95%→14.0 周期,50%→104 周期。 注意 99% 那行只比全命中多 2 个周期——而 90% 那行已经是 6 倍。 多级叠加的结果是 5.8 周期——三层各 90% 的命中率,合起来的效果相当于一层 99.9% 的缓存。 这就是【层层挡住】的威力:不需要每层都很强,只需要每层都挡住一大半。 反过来:如果某一层命中率崩了(比如工作集突然变大),上面几层跟着一起崩——这就是【缓存雪崩】在 CPU 里的版本。
📋 Linux 上量真实的命中率:perf stat -e L1-dcache-loads,L1-dcache-load-misses <命令>——两个数一除就是命中率。
30 秒实验
- 把公式里的缺失代价从 200 改成 400,看命中率 95% 时平均时间变多少。
perf stat -e L1-dcache-load-misses,L1-dcache-loads ls,算一次真实命中率。- 把多级模型改成每层 95%,看最终平均时间掉到多少。
常见误区
- 【命中率 95% 已经很好了】。要看缺失代价:代价 200 周期时,95% 意味着平均慢 3.5 倍。
- 【做一层特别大的缓存更简单】。大就慢,慢到那个程度它就不是缓存了。
- 【缓存命中率只能靠硬件】。程序的数据布局与访问顺序直接决定命中率,软件能做的一大半都在这里。
自测三问(点开对答案)
1. 平均访问时间的公式是什么? —— **命中开销 + 缺失率 × 缺失代价。**它说明**缺失那部分被放大了几十倍,所以命中率极度敏感。** 2. 为什么多级缓存比一级大缓存有效? —— **每一层只兜住上一层的 10%**,**层层下来绝大多数访问被最便宜的那层接住。** 3. 三层各 90% 命中率,合起来相当于多少? —— **大约相当于一层 99.9% 的缓存**(实测平均 5.8 周期)。**不需要每层都强,只需要每层都挡住一大半。**- ☐ 6.7 转译后备缓冲区
相关笔记
- 高速缓存不足时 — 命中率怎么崩
- 转译后备缓冲区 — 另一张也要缓存的表
- 多级页表 — 分级思路的另一处应用
- 🐧 Linux是怎样工作的 · 索引