为什么大多数处理器的L1缓存大小比L2缓存小?

42
为什么大多数处理器中 L1 缓存的大小要比 L2 缓存小?

1
在SuperUser上有一个相关的问题:“处理器中的多级缓存实际上是什么?”(http://superuser.com/q/269080/180742) - user2467198
1
相关:http://electronics.stackexchange.com/questions/104759/why-is-l1-cache-faster-than-l2-cache - Ciro Santilli OurBigBook.com
7个回答

58
L1与CPU核心紧密耦合,在每次内存访问时(非常频繁)都会被访问。因此,它需要在很短的时间内返回数据(通常在一个时钟周期内)。对于L1数据缓存来说,延迟和吞吐量(带宽)都是性能关键因素(例如,四个周期的延迟,并且支持CPU核心每个时钟周期的两个读取和一个写入)。它需要大量的读/写端口来支持这种高访问带宽。构建具有这些属性的大型缓存是不可能的。因此,设计人员将其保持较小,例如,在今天的大多数处理器中为32KB。
L2仅在L1未命中时访问,因此访问频率较低(通常为L1的1/20)。因此,L2可以具有更高的延迟(例如从10到20个周期)并具有较少的端口。这使得设计人员可以使其更大。
L1和L2发挥非常不同的作用。如果增加L1大小,将增加L1访问延迟,这将大大降低性能,因为它会使所有相关负载更慢,并且难以通过乱序执行来隐藏。L1大小几乎没有争议。
如果我们移除L2,L1 miss必须到达下一级(比如内存)。这意味着很多访问将会去到内存,这意味着我们需要更多的存储器带宽,它已经是一个瓶颈了。因此,保留L2是有利的。
专家们经常将L1称为延迟过滤器(因为它使L1命中的情况更快),而L2则被称为带宽过滤器,因为它降低了存储器带宽使用量。
注意:我假设我的论点中有一个2级高速缓存层次结构,以使其更简单。在今天的许多多核芯片中,每个核心都有自己的私有L1和L2,同时共享一个L3高速缓存。在这些芯片中,共享的最后一级缓存(L3)起到了内存带宽过滤器的作用。 L2起到芯片内带宽过滤器的作用,即减少对芯片内部互连和L3的访问。这允许设计人员使用低带宽的互连方式,如环形,并使用慢速的单端口L3,这允许他们使L3更大。
也许值得一提的是,端口数量是一个非常重要的设计点,因为它影响高速缓存消耗多少芯片面积。端口会向高速缓存添加线路,这将消耗大量的芯片面积和功率。

我在两个地方更正了数字(一个单周期延迟的L1必须是VIVT,而且要简单得多),但最终还是写了自己的答案来表达我想说的一切。请参见我的答案以获取更多详细信息,以及实际AMD和Intel CPU的延迟/大小/关联性的真实数字。它们的高速缓存层次结构非常不同。 - Peter Cordes

40

这是有不同原因的。

L2存在于系统中,以加快当L1缓存未命中的情况。如果L1的大小与L2相同或更大,则L2将无法容纳比L1更多的缓存行,并且无法处理L1缓存未命中。从设计/成本角度来看,L1缓存与处理器绑定,比L2更快。缓存的整个思想是通过添加比最慢的硬件性能更高(并昂贵)但比您拥有的更快的硬件便宜的中间硬件来加速访问较慢的硬件。即使您决定将L1缓存加倍,也会增加L2以加速L1缓存未命中。

那么为什么还需要L2缓存呢?好吧,L1缓存通常更具性能和建造成本更高,并且它绑定到单个核心。这意味着,将L1大小增加固定数量将导致在双核处理器中将该成本乘以4,或者在四核处理器中将其乘以8。 L2通常由不同的内核共享--根据体系结构可以在处理器中的一对或所有内核之间共享,因此即使L1和L2的价格相同--这是不可能的,增加L2的成本也会更小。


1
我知道这是一个旧答案,但是无论缓存的数量或核心数如何,将L1缓存加倍都会使成本翻倍。 - Fascia
1
@Fascia:你是完全正确的(我的数学老师今天会怎么说!)。我尝试重新措辞以达到预期的含义:增加L1的成本将乘以核心数,因为它不是共享的。 - David Rodríguez - dribeas
@DavidRodríguez-dribeas:我并不是说你错了。L2的存在是为了加速L1缓存未命中的情况L2在系统中的存在是为了加速L1缓存未命中更清晰。我认为加速缓存未命中有点误导性。 - arunmoezhi
@arunmoezhi:我明白了,我希望得到重写建议,但没有意识到评论已经提供了这样的建议。我已经更新了答案。 - David Rodríguez - dribeas
L1的物理结构是什么,它比L2更昂贵/更快的原因是什么?这个答案解释了一些:http://superuser.com/a/724537/128124 - Ciro Santilli OurBigBook.com

39

@Aater的回答解释了一些基础知识。我将添加一些更多细节和实际缓存组织的示例,包括延迟和其他属性,而不仅仅是大小。

有关IvyBridge的一些详细信息,请参见{{link2:“缓存为什么这么快?”我的回答},其中讨论了整体负载使用延迟,包括地址计算时间以及不同缓存级别之间数据总线的宽度。


L1需要非常快(延迟和吞吐量),即使这意味着有限的命中率。 L1d还需要在几乎所有架构上支持单字节存储,并且(在某些设计中)支持非对齐访问。这使得使用ECC(纠错码)保护数据变得困难,事实上,一些L1d设计(Intel)仅使用奇偶校验,在更外层的高速缓存(L2 / L3)中具有更好的ECC,因为可以对更大的块进行ECC以降低开销。
不可能设计出一个单一级别的缓存,能够提供现代多级缓存的低平均请求延迟(在所有命中和未命中中平均)。由于现代系统拥有多个非常饥饿的核心,它们共享与相对高延迟的DRAM的连接,这是必要的。
每个核心都需要自己的私有L1以提高速度,但至少最后一级缓存通常是共享的,因此从多个线程读取相同数据的多线程程序不必在每个核心上为其访问DRAM。(并作为一个由一个核心写入并由另一个核心读取的数据的支持)。这要求对于健全的多核系统至少需要两级缓存, 这是现代设计中超过2级的动力之一。现代多核x86 CPU在每个核心中都有快速的2级缓存和由所有核心共享的更大、更慢的缓存。
L1命中率仍然非常重要,因此L1缓存不如可能的那么小/简单/快,因为这会降低命中率。因此,实现相同的总体性能需要更高级别的缓存更快。如果高级别处理更多的流量,它们的延迟就成为平均延迟的一个更大的组成部分,并且它们更经常地成为吞吐量的瓶颈(或需要更高的吞吐量)。
高吞吐量通常意味着能够每个周期处理多个读写,即多端口。与低吞吐量缓存相比,这需要更多的面积和功率来获得相同的容量,因此这是L1保持小的另一个原因。

L1还使用速度技巧,如果它更大就行不通。即大多数设计使用虚拟索引,物理标记(VIPT) L1,但所有索引位都来自页面偏移以下,因此它们的行为类似于PIPT(因为虚拟地址的低位与物理地址相同)。这避免了同义词/同音词(误命中或相同数据在缓存中出现两次,请参见Paul Clayton在链接问题上的详细答案),但仍然允许部分命中/错过检查与TLB查找同时发生。VIVT高速缓存不必等待TLB,但必须在每次更改页面表时使其失效。

在x86架构中(使用4kiB虚拟内存页),32kiB 8路组相联L1缓存在现代设计中很常见。由于这些位于虚拟地址和物理地址下方的低12位是相同的(它们低于4kiB页面的页偏移量),因此可以基于虚拟地址的低12位获取8个标记。这种针对L1缓存的速度优化只有在它们足够小且关联度足够高以至于索引不依赖于TLB结果时才有效。32kiB / 64B行 / 8路组相联 = 64 (2^6) 组。因此,地址的最低6位选择线内的字节,并且接下来的6位索引了8个标记的一组。这组8个标记与TLB查找并行获取,因此可以与TLB结果的物理页面选择位并行检查这些标记,以确定缓存的8个路中哪些(如果有)包含数据。(PIPT L1缓存的最小关联度,也可作为VIPT访问,无需将索引转换为物理地址
扩大L1高速缓存意味着它必须在开始获取标记并将其加载到并行比较器之前等待TLB结果,或者它必须增加关联性以使log2(sets)+ log2(line_size)<= 12。 (更高的关联性意味着每组更多的方式=>更少的总组=更少的索引位)。因此,例如,64kiB缓存需要是16路关联的:仍然有64个组,但每个组有两倍的路数。这使得将L1大小增加到当前大小以上在功耗方面变得难以承受,并且可能会增加延迟。
在L1D缓存逻辑上花费更多的功率预算会剩下较少的功率可用于乱序执行、解码和当然还有L2缓存等。要使整个核心在高ILP代码上以4GHz运行并保持每个时钟周期约4条指令(without melting),需要平衡设计。请参阅本文:现代微处理器:90分钟指南!

缓存越大,清除它会造成的损失就越大,因此一个大的VIVT L1缓存将比当前的VIPT-that-works-like-PIPT更糟糕。而一个更大但延迟更高的L1D也很可能更差。

根据@PaulClayton的说法,L1缓存通常与标记一起并行获取集合中的所有数据,因此一旦检测到正确的标记,它们就已经准备好被选择了。这样做的功耗随关联度增加而增加,因此一个大的高关联度L1不仅面积大、延迟高,而且能耗也很高。(与L2和L3相比,它的面积不算很大,但物理位置对于延迟很重要。当时钟周期为1/4纳秒时,光速传播延迟很重要。)

较慢的缓存(例如L3)可以以更低的电压/时钟速度运行,以产生更少的热量。它们甚至可以使用每个存储单元的不同晶体管排列,以制作更适用于功耗而非高速的存储器。

多级缓存有很多与功耗相关的原因。在现代CPU设计中,功率/热量是最重要的约束之一,因为冷却小芯片很困难。在速度和功耗(和/或晶片面积)之间需要做出权衡。此外,许多CPU由电池供电或位于需要额外冷却的数据中心。


L1通常被分成单独的指令和数据缓存。 与支持代码获取的统一L1中的额外读取端口不同,我们可以有一个连接到单独I-TLB的单独的L1I缓存。(现代CPU通常具有L2-TLB,这是第二级缓存用于翻译,由L1 I-TLB和D-TLB共享,而不是常规L2缓存使用的TLB)。这使我们总共拥有64kiB的L1缓存,静态地分区为代码和数据缓存,比具有相同总吞吐量的64k L1统一缓存要便宜得多(并且可能具有更低的延迟)。由于代码和数据之间通常几乎没有重叠,因此这是一个巨大的优势。
L1I可以物理上靠近代码获取逻辑,而L1D可以物理上靠近加载/存储单元。当一个时钟周期只持续1/3纳秒时,光速传输线延迟是一个大问题。布线也是一个大问题:例如Intel Broadwell在硅上方有13层铜层
分离的L1对于速度有很大帮助,但统一的L2是最好的选择。一些工作负载具有非常小的代码,但涉及大量数据。更高级别的缓存应该是统一的,以适应不同的工作负载,而不是静态地分区为代码和数据。(例如,在运行大矩阵乘法时,几乎所有L2都将缓存数据,而不是代码,而在运行膨胀的C++程序或者复杂算法的有效实现(例如运行gcc)时,则会有很多热代码)。代码可以像数据一样被复制,而不仅仅是通过DMA从磁盘加载到内存中。
缓存还需要逻辑来跟踪未完成的缺失(由于乱序执行,因此在解决第一个缺失之前可以继续生成新请求)。有很多未完成的缺失意味着您重叠了缺失的延迟,从而实现了更高的吞吐量。在L2中复制逻辑和/或静态分区代码和数据也不是好的选择。
较大的低流量缓存也是放置预取逻辑的好地方。硬件预取使得诸如循环数组之类的操作性能良好,而不需要每个代码片段都需要软件预取指令。(SW预取曾经非常重要,但是HW预取器比以前聪明,因此Ulrich Drepper's otherwise excellent What Every Programmer Should Know About Memory中的建议已经过时了,对于许多用例而言,这不再适用。)
低流量的高级缓存可以承受延迟,从而做一些聪明的事情,例如使用自适应替换策略而不是通常的LRU。 英特尔IvyBridge及更高版本的CPU也采用了这种方法,以抵抗访问模式,该模式对于一个稍微大一点无法放入缓存的工作集没有任何缓存命中。 (例如,两次沿着相同方向循环遍历某些数据意味着它可能会在重新使用之前被驱逐。)
一个真实的例子:英特尔Haswell。资料来源:David Kanter的微架构分析Agner Fog的测试结果(微架构pdf)。还可以参考英特尔的优化手册(链接在标签维基中)。

此外,我还写了一篇关于Intel Core i7处理器使用哪种高速缓存映射技术?的单独答案。

现代英特尔设计使用一个大型包容性L3缓存,由所有核心共享,作为高速缓存一致性流量的后备。它在物理上分布在各个核心之间,每个核心有2048组* 16路(2MiB)(IvyBridge及更高版本中采用自适应替换策略)。较低级别的缓存是每个核心专用的。
  • L1:每个内核32kiB指令和数据(分开),8路组相连。 延迟=4个时钟周期。至少有2个读端口+1个写端口。(甚至可能有更多端口来处理L1和L2之间的流量,或者从L2接收缓存行与撤销存储冲突。)可以跟踪10个未完成的缓存未命中(10个填充缓冲区)。
  • L2:每个内核256kiB统一,8路组相连。 延迟=11或12个时钟周期。读带宽:64字节/周期。主预取逻辑预取到L2。可以跟踪16个未完成的未命中。可以每个周期向L1I或L1D提供64B。实际端口计数未知。
  • L3:统一的、共享的(所有内核共用)8MiB(对于四核i7)。包容性的(包括所有L2和L1每个内核高速缓存)。12或16路组相连。 延迟=34个时钟周期。作为缓存一致性的后备,因此修改的共享数据不必出去到主存再返回。

另一个真实的例子:AMD Piledriver:(例如Opteron和桌面FX CPU)。缓存行大小仍然是64B,就像英特尔和AMD多年来一直使用的那样。文本主要摘自Agner Fog的微架构pdf,加上我找到的一些幻灯片的额外信息,更多关于写通L1 + 4k写组合高速缓存 在Agner的博客上,并且有一个评论说只有L1是WT,而不是L2

  • L1I: 64 kB,2路,共享于一对核心之间(AMD的SMD版本比超线程分区更多,他们称每个分区为一个核心。每对核心共享一个矢量/FPU单元和其他流水线资源。)
  • L1D: 16 kB,4路,每个核心。延迟=3-4 c。 (请注意,页面偏移以下的所有12位仍用于索引,因此通常的VIPT技巧有效。)(吞吐量:每时钟周期两个操作,其中一个可能是存储操作)。策略=写穿透,带有4k写合并缓存。
  • L2: 2 MB,16路,由两个核心共享。延迟=20个时钟周期。读取吞吐量每4个时钟周期1次。写入吞吐量每12个时钟周期1次。
  • L3: 0-8 MB,64路,由所有核心共享。延迟=87个时钟周期。读取吞吐量每15个时钟周期1次。写入吞吐量每21个时钟周期1次。
Agner Fog报告称,当一对核心都处于活动状态时,L1吞吐量低于另一半处于空闲状态时的吞吐量。目前尚不清楚发生了什么情况,因为L1缓存应该是针对每个核心分别独立的。

我查看了cpuid的叶子0x02的描述,并注意到第二和第三级缓存没有任何数据/指令分离。所以所有当前的x86 CPU都有“统一”的第二和第三级缓存? - St.Antario
2
@St.Antario:是的,当然。我认为这是一个众所周知的事实。这就是为什么这个答案使用L1I / L1D,但只使用L2或L3。分离L1可以为代码和数据提取提供更多带宽,并且基本上是较大、较慢的外部缓存的带宽过滤器。我从未听说过任何CPU在L1之外使用分离高速缓存,即使在x86之外也是如此。例如,What does a 'Split' cache means. And how is it useful(if it is)? 将“修改后的哈佛”定义为分离L1和统一的内存层次结构的其余部分,因为nobody使用分离L2。 - Peter Cordes

4
这里的其他答案给出了L1和L2大小的具体技术原因,虽然它们中的许多是特定架构的动机考虑,但它们并不是真正必要的:从内核移开时导致增加(私有)缓存大小的基本架构压力是相当普遍的,并且与首次使用多个缓存的推理相同。
三个基本事实是:
1. 大多数应用程序的内存访问表现出高度的时间局部性,具有非均匀分布。 2. 在大量的进程和设计中,可以在缓存大小和缓存速度(延迟和吞吐量)之间进行权衡。 3. 每个不同级别的缓存都涉及增量设计和性能成本。
因此,在基本水平上,您可能能够说将缓存的大小加倍,但与较小的缓存相比会产生1.4的延迟惩罚。
因此,它变成了一个优化问题:您应该拥有多少个缓存以及它们应该有多大?如果工作集大小内的内存访问完全均匀,您可能最终会得到一个相当大的单个缓存或根本没有缓存。然而,访问是强烈非均匀的,因此小而快的缓存可以捕获大量访问,与其大小不成比例。
如果事实2不存在,您只需在芯片的其他限制条件下创建一个非常大且非常快速的L1缓存,并且不需要任何其他缓存级别。
如果事实3不存在,您将最终拥有大量细粒度的“缓存”,中心更快而小,外部更慢而更大,或者可能是具有可变访问时间的单个缓存:对于最靠近核心的部分更快。实际上,规则3意味着每个缓存级别都有额外的成本,因此您通常会得到一些量化的缓存级别2
其他约束
这提供了一个基本框架来理解缓存计数和缓存大小决策,但还有一些次要因素在起作用。例如,Intel x86具有4K页面大小,它们的L1缓存使用VIPT体系结构。VIPT意味着缓存大小除以路数不能大于4 KiB3。因此,在半打英特尔设计中使用的8路L1缓存最多只能为4 KiB * 8 = 32 KiB。可能并非巧合,这正好是这些设计中L1缓存的大小!如果没有这个限制,完全有可能看到更低关联性和/或更大的L1缓存(例如64 KiB,4路)。

1 当然,还有其他因素影响着权衡,例如面积和功耗,但在保持这些因素不变的情况下,大小和速度之间的权衡适用,并且即使这些因素没有保持不变,基本行为也是相同的。

2 除了这种压力外,像大多数L1设计一样,已知延迟缓存还具有调度优势:乱序调度器可以乐观地提交依赖于内存加载的操作,以便在L1缓存返回的周期上读取旁路网络的结果。这减少了争用,或许从关键路径上切掉了一个周期的延迟。这对最内层的缓存级别施加了一定的压力,要求其具有统一/可预测的延迟,可能会导致较少的缓存级别。

3 原则上,您可以在没有此限制的情况下使用VIPT高速缓存,但只能通过需要操作系统支持(例如页面颜色)或其他约束条件来实现。x86架构尚未做到这一点,现在可能无法开始。


3

@Chiffre:你的意思是说L1缓存和L2缓存采用了不同的内存技术吗? - Karthik Balaguru
1
@S.Man:是的,每种缓存类型的成本都不同于其他类型。一般来说,性能和成本的关系如下:L1 > L2 > [L3 >] 主内存。否则,您将使用最快的技术以相同的成本建立所有内容。这将不会使L1的大小比L2大,而只是删除L2并使用L1+L2的累积大小构建L1。 - David Rodríguez - dribeas
@S.Man:对于单核处理器,如果L1和L2的性能(和成本,您将支付提供性能的更便宜技术)相同,则拥有1M L1和2M L2的成本与拥有3M L1和没有L2的成本相同。缓存大小增加三倍,缓存未命中的数量会更少,系统在相同成本下更快。 - David Rodríguez - dribeas
如果你愿意花一些时间了解这个话题,我建议你阅读一下这篇维基百科文章:https://secure.wikimedia.org/wikipedia/en/wiki/Level_1_cache#Multi-level_caches,里面甚至有一个相当不错的多级内存结构图表! - basti
@David:如果使用最快的技术来处理L1和L2,那么取消L2并仅保留更大的L1是一个好主意! - Karthik Balaguru
@S.Man:但在多核CPU时代,您需要L2缓存来管理不同核心之间的负载分配。因为据我所知,L1是每个核心的,而L2是每个CPU的。 - basti

3

1
我也可以推荐威廉·斯特林斯(William Stallings)的《计算机组成与体系结构》第八版(第四章)。 - Lyuben Todorov

-1

从逻辑上讲,这个问题的答案已经很明显了。

如果L1比L2(合并后)更大,那么就不需要L2缓存了。

如果你可以把所有东西都存储在硬盘驱动器上,为什么还要把它们存储在磁带驱动器上呢?


4
您的回答假设读者具有 CPU 缓存的基本理解,这可能不适用于许多感兴趣的读者。此外,它没有为当前的答案增添任何额外的价值。 - FreeAsInBeer
1
L2可以是高度关联的受害者缓存,以减少L1中冲突缺失的惩罚。尤其是如果L1很简单,例如直接映射。在这种情况下,即使L2比L1小得多,L2也可能非常有用。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接