37

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

4

7 回答 7

55

L1 与 CPU 内核非常紧密地耦合,并且在每次内存访问时都会被访问(非常频繁)。因此,它需要非常快地返回数据(通常在时钟周期内)。延迟和吞吐量(带宽)对于 L1 数据缓存来说都是性能关键。(例如四个周期延迟,并支持CPU内核每个时钟周期两次读取和一次写入)。它需要大量的读/写端口来支持这种高访问带宽。使用这些属性构建大型缓存是不可能的。因此,设计人员将其保持在较小的范围内,例如当今大多数处理器中的 32KB。

L2 仅在 L1 未命中时访问,因此访问频率较低(通常为 L1 的 1/20)。因此,L2 可以具有更高的延迟(例如从 10 到 20 个周期)并且具有更少的端口。这使设计师可以将其做得更大。


L1 和 L2 扮演着非常不同的角色。如果 L1 变得更大,它将增加 L1 访问延迟,这将大大降低性能,因为它会使所有相关负载变得更慢,更难隐藏乱序执行。L1 大小几乎没有争议。

如果我们删除了 L2,L1 未命中将不得不进入下一个级别,比如内存。这意味着大量访问将进入内存,这意味着我们需要更多的内存带宽,这已经是一个瓶颈。因此,保持 L2 左右是有利的。

专家们经常将 L1 称为延迟过滤器(因为它使 L1 命中的常见情况更快),将 L2 称为带宽过滤器,因为它减少了内存带宽的使用。

注意:我在参数中假设了 2 级缓存层次结构以使其更简单。在当今的许多多核芯片中,所有内核之间共享一个 L3 缓存,而每个内核都有自己的私有 L1,也许还有 L2。在这些芯片中,共享的最后一级缓存(L3)起到内存带宽过滤器的作用。L2 起到片上带宽过滤器的作用,即减少对片上互连和L3 的访问。这允许设计人员使用像环这样的带宽较低的互连,以及速度较慢的单端口 L3,这使他们能够使 L3 更大。

或许值得一提的是,端口数量是一个非常重要的设计点,因为它会影响缓存消耗多少芯片面积。端口将线路添加到缓存中,这会消耗大量芯片面积和功率。

于 2011-05-18T18:49:18.567 回答
39

有不同的原因。

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

那么为什么会有 L2 缓存呢?嗯,L1 缓存通常性能更高,构建成本更高,并且绑定到单个内核。这意味着将 L1 大小增加一个固定数量将使双核处理器的成本乘以 4,或在四核处理器中乘以 8。L2 通常由不同的内核共享——取决于架构,它可以在处理器中的几个或所有内核之间共享,因此即使 L1 和 L2 的价格相同,增加 L2 的成本也会更小——这它不是。

于 2011-01-12T08:55:21.360 回答
32

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

有关 IvyBridge 的一些详细信息,请参阅我的回答“缓存怎么能这么快?” ,并讨论了总体加载使用延迟,包括地址计算时间,以及不同级别缓存之间的数据总线宽度。


L1 需要非常快(延迟和吞吐量),即使这意味着有限的命中率。L1d 还需要支持几乎所有架构上的单字节存储,以及(在某些设计中)非对齐访问。这使得很难使用 ECC(纠错码)来保护数据,事实上一些 L1d 设计(英特尔)只使用奇偶校验,只有在可以完成 ECC 的外部缓存级别(L2/L3)中具有更好的 ECC在更大的块上以降低开销。

不可能设计出可以提供现代多级缓存的低平均请求延迟(所有命中和未命中的平均值)的单级缓存。由于现代系统有多个非常饥饿的内核,它们都共享到同一个延迟相对较高的 DRAM 的连接,因此这是必不可少的。

每个核心都需要自己的私有 L1 以提高速度,但至少最后一级缓存通常是共享的,因此从多个线程读取相同数据的多线程程序不必在每个核心上为它去 DRAM。(并充当一个核心写入并由另一个核心读取的数据的支持)。 对于一个健全的多核系统,这需要至少两级缓存,并且是当前设计中多于两级的动机的一部分。现代多核 x86 CPU 在每个内核中都有一个快速的 2 级缓存,并且所有内核共享一个更大的较慢缓存。

L1 命中率仍然非常重要,因此 L1 缓存没有尽可能小/简单/快,因为这会降低命中率。因此,实现相同的整体性能需要更高级别的缓存才能更快。如果更高级别处理更多流量,则它们的延迟是平均延迟的更大组成部分,并且它们更频繁地成为其吞吐量的瓶颈(或需要更高的吞吐量)。

高吞吐量通常意味着能够在每个周期处理多个读取和写入,即多个端口。与较低吞吐量的缓存相同的容量,这需要更多的面积和功率,因此这是 L1 保持较小的另一个原因。


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

在 x86(使用 4kiB 虚拟内存页面)上,32kiB 8 路关联 L1 缓存在现代设计中很常见。可以根据虚拟地址的低 12 位来获取 8 个标签,因为这些位在虚拟地址和物理地址中是相同的(它们低于 4kiB 页面的页面偏移量)。仅当 L1 缓存足够小且关联性足够大以至于索引不依赖于 TLB 结果时,这种针对 L1 缓存的速度破解才有效。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 缓存等的功率。让整个内核以 4GHz 运行并在不熔化的情况下维持每时钟约 4 条指令(在高 ILP 代码上)需要平衡设计。请参阅这篇文章:现代微处理器:90 分钟指南!.

缓存越大,刷新它的损失就越大,因此大型 VIVT L1 缓存会比当前的 VIPT-that-works-like-PIPT 更糟糕。更大但延迟更高的 L1D 可能也会更糟。

根据@PaulClayton 的说法,L1 缓存通常与标签并行获取一组数据,因此一旦检测到正确的标签,就可以选择它。这样做的功耗与关联性成正比,因此大型的高关联 L1 对功耗以及裸片面积(和延迟)都是非常不利的。(与 L2 和 L3 相比,它的面积不会很大,但物理接近对延迟很重要。当时钟周期为 1/4 纳秒时,光速传播延迟很重要。)

较慢的缓存(如 L3)可以以较低的电压/时钟速度运行以减少热量。他们甚至可以为每个存储单元使用不同的晶体管排列方式,以使内存在功率方面比在高速方面更优化。

多级缓存有很多与功耗相关的原因。功率/热量是现代 CPU 设计中最重要的限制之一,因为很难冷却一个微型芯片。一切都是速度和功率(和/或芯片面积)之间的权衡。此外,许多 CPU 由电池供电,或者位于需要额外冷却的数据中心。


L1 几乎总是被分成单独的指令和数据缓存。 我们可以将单独的 L1I 高速缓存绑定到单独的 I-TLB,而不是在统一的 L1 中使用额外的读取端口来支持代码获取。(现代 CPU 通常有一个 L2-TLB,它是用于翻译的二级缓存,由 L1 I-TLB 和 D-TLB 共享,而不是常规 L2 缓存使用的 TLB)。这为我们提供了总共 64kb 的 L1 缓存,静态划分为代码和数据缓存,比具有相同总吞吐量的怪物 64k L1 统一缓存便宜得多(并且可能延迟更低)。由于代码和数据之间通常很少有重叠,因此这是一个巨大的胜利。

L1I 可以物理上靠近代码获取逻辑,而 L1D 可以物理上靠近加载/存储单元。当一个时钟周期仅持续 1/3 纳秒时,光速传输线延迟是一个大问题。布线也很重要:例如英特尔 Broadwell 在硅片上方有 13 层铜

拆分 L1 对速度有很大帮助,但统一 L2 是最佳选择。 一些工作负载的代码非常小,但涉及大量数据。统一更高级别的缓存以适应不同的工作负载是有意义的,而不是静态地划分为代码与数据。(例如,几乎所有的 L2 都将缓存数据,而不是代码,同时运行一个大矩阵乘法,而不是在运行一个臃肿的 C++ 程序时有很多代码热,甚至是一个复杂算法的有效实现(例如运行 gcc) )。代码可以作为数据复制,而不是总是使用 DMA 从磁盘加载到内存中。


缓存还需要逻辑来跟踪未完成的未命中(因为无序执行意味着在第一次未命中解决之前可以继续生成新请求)。有许多未命中意味着您可以重叠未命中的延迟,从而实现更高的吞吐量。在 L2 中复制代码和数据之间的逻辑和/或静态分区并不好。

较大的低流量缓存也是放置预取逻辑的好地方。硬件预取可为诸如循环数组之类的事情提供良好的性能,而无需每段代码都需要软件预取指令。(软件预取在一段时间内很重要,但硬件预取器比以前更聪明了,因此Ulrich Drepper 的其他优秀的关于内存的每个程序员都应该了解的建议对于许多用例来说已经过时了。)

低流量的高级缓存可以提供延迟来做一些聪明的事情,比如使用自适应替换策略而不是通常的 LRU。 英特尔 IvyBridge 和更高版本的 CPU 会这样做,以抵制对工作集没有缓存命中的访问模式,因为工作集稍微太大而无法放入缓存。(例如,在同一方向上循环一些数据两次意味着它可能在被重用之前被驱逐。)


一个真实的例子:英特尔 Haswell。资料来源:David Kanter 的微架构分析Agner Fog 的测试结果 (microarch pdf)。另请参阅 Intel 的优化手册(标签 wiki 中的链接)。

另外,我写了一个单独的答案:英特尔酷睿 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,就像 Intel 和 AMD 已经使用了几年一样。文本主要从 Agner Fog 的 microarch pdf 复制,附加信息来自我找到的一些幻灯片,以及Agner 博客上关于直写式 L1 + 4k 写入组合缓存的更多详细信息,并评论说只有 L1 是 WT,而不是 L2

  • L1I:64 kB,2路,在一对内核之间共享(AMD的SMD版本比超线程有更多的静态分区,他们称每个内核为一个内核。每对共享一个向量/FPU单元和其他管道资源。)
  • L1D:16 kB,4 路,每个内核。延迟 = 3-4 c。(请注意,页面偏移量以下的所有 12 位仍用于索引,因此通常的 VIPT 技巧有效。)(吞吐量:每个时钟两个操作,其中一个是存储)。 Policy = Write-Through,具有 4k 写入组合缓存。
  • L2:2 MB,16 路,在两个内核之间共享。延迟 = 20 个时钟。每 4 个时钟读取吞吐量 1。每 12 个时钟写入吞吐量 1。
  • L3:0 - 8 MB,64 路,在所有内核之间共享。 延迟 = 87 时钟。每 15 个时钟读取吞吐量 1。每 21 个时钟写入吞吐量 1

Agner Fog 报告说,当一对中的两个内核都处于活动状态时,L1 吞吐量低于一对中另一半处于空闲状态时的吞吐量。不知道发生了什么,因为 L1 缓存应该对每个核心都是分开的。

于 2016-07-24T07:31:14.007 回答
4

这里的其他答案给出了 L1 和 L2 大小的具体和技术原因,虽然其中许多是针对特定架构的动机考虑因素,但它们并不是真正必要的:导致增加(私有)缓存大小的底层架构压力当您远离核心时,这是相当普遍的,并且与首先使用多个缓存的推理相同。

三个基本事实是:

  1. 大多数应用程序的内存访问表现出高度的时间局部性,分布不均匀。
  2. 在各种工艺和设计中,缓存大小和缓存速度(延迟和吞吐量)可以相互权衡1
  3. 每个不同级别的缓存都涉及增量设计和性能成本。

因此,在基本层面上,您可以说缓存的大小翻倍,但与较小的缓存相比,会产生 1.4 的延迟损失。

所以它变成了一个优化问题:你应该有多少个缓存,它们应该有多大?如果内存访问在工作集大小内完全一致,那么您最终可能会得到一个相当大的缓存,或者根本没有缓存。然而,访问是非常不均匀的,所以一个小而快的缓存可以捕获大量的访问,与它的大小不成比例。

如果事实 2 不存在,您只需在芯片的其他限制范围内创建一个非常大、非常快的 L1 缓存,并且不需要任何其他缓存级别。

如果事实 3 不存在,您最终会得到大量细粒度的“缓存”,中心更快更小,外部更慢且更大,或者可能是具有可变访问时间的单个缓存:更快最靠近核心的部分。在实践中,规则 3 意味着每个级别的缓存都有额外的成本,因此您通常会得到几个量化级别的缓存2

其他约束

这为理解缓存计数和缓存大小决策提供了一个基本框架,但也有次要因素在起作用。例如,Intel x86 的页面大小为 4K,其 L1 缓存使用VIPT架构。VIPT 表示缓存大小除以路数不能大于3大于 4 KiB。因此,在六种英特尔设计中使用的 8 路 L1 缓存最多可以为 4 KiB * 8 = 32 KiB。在这些设计中,这正是 L1 缓存的大小可能并非巧合!如果没有这个限制,您完全有可能看到较低的关联性和/或较大的 L1 缓存(例如,64 KiB,4 路)。


1当然,权衡中还涉及其他因素,例如面积和功率,但保持这些因素不变,适用尺寸-速度权衡,即使不保持不变,基本行为也是相同的。

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

3原则上,您可以在没有此限制的情况下使用 VIPT 缓存,但仅需要操作系统支持(例如,页面着色)或有其他限制。x86 架构还没有做到这一点,现在可能无法启动。

于 2018-04-16T18:25:24.747 回答
3

对于那些对这类问题感兴趣的人,我的大学推荐Computer Architecture: A Quantitative ApproachComputer Organization and Design: The Hardware/Software Interface。当然,如果您没有时间,可以在Wikipedia上获得快速概览。

于 2012-02-13T01:12:34.777 回答
2

我认为主要原因是,L1-Cache 更快,因此更昂贵。

https://en.wikichip.org/wiki/amd/microarchitectures/zen#Die

例如,比较 AMD Zen 内核的 L1、L2 和 L3 缓存物理大小。密度随着缓存级别的增加而急剧增加。

于 2011-01-12T08:42:58.247 回答
-1

从逻辑上讲,这个问题自己回答了。

如果 L1 大于 L2(组合),则不需要 L2 Cache。

如果可以将所有内容存储在 HDD 上,为什么还要将其存储在磁带驱动器上?

于 2013-02-08T08:44:23.323 回答