Aat*_*man 46

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

仅在L1未命中时才访问L2,因此访问频率较低(通常是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更大.

也许值得一提的是端口数量是一个非常重要的设计点,因为它会影响缓存消耗的芯片面积.端口向缓存添加线路,这消耗了大量芯片面积和功率.


Dav*_*eas 32

这有不同的原因.

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

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


Pet*_*des 21

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

有关IvyBridge的一些细节,请参阅我的回答"如何快速缓存?" ,讨论了整体负载使用延迟,包括地址计算时间,以及不同级别缓存之间数据总线的宽度.


L1需要非常快(延迟和吞吐量),即使这意味着有限的命中率.L1d还需要支持几乎所有体系结构上的单字节存储,以及(在某些设计中)未对齐访问.这使得很难使用ECC(纠错码)来保护数据,实际上一些L1d设计(英特尔)只使用奇偶校验,只有在外部缓存级别(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位来获取8个标签,因为这些位在虚拟和物理地址中是相同的(它们低于4kiB页面的页面偏移).L1缓存的这种速度攻击只有在它们足够小并且足够关联以使索引不依赖于TLB结果时才有效.32kiB/64B线/ 8路相关性= 64(2 ^ 6)组.因此,一行中最低6位的地址选择字节,接下来的6位索引一组8个标记.这组8个标签与TLB查找并行获取,因此可以与TLB结果的物理页选择位并行检查标签,以确定缓存的8种方式中的哪些(如果有的话)保存数据.

制作更大的L1缓存意味着它必须等待TLB结果才能开始获取标记并将它们加载到并行比较器中,否则它必须增加关联性以保持log2(sets)+ log2(line_size) <= 12.(更多关联性意味着每组更多的方式=>更少的总集数=更少的索引位).因此,例如64kiB缓存需要16路关联:仍然是64套,但每组的方式是两倍.这使得增加的L1尺寸超出当前尺寸在功率方面非常昂贵,甚至可能甚至延迟.

在L1D高速缓存逻辑上花费更多的功率预算会为乱序执行,解码,当然还有L2高速缓存等留下更少的功率.让整个内核运行在4GHz并且每个时钟维持~4个指令(在高ILP代码上)而不需要熔化需要平衡设计.参见这篇文章:现代微处理器:90分钟指南!.

缓存越大,通过刷新它就会损失得越多,因此大型VIVT L1缓存会比当前的VIPT更糟糕 - 就像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).这为我们提供了64kiB的L1缓存,静态分区为代码和数据缓存,比具有相同总吞吐量的怪物64k L1统一缓存便宜得多(并且可能更低的延迟).由于代码和数据之间通常很少重叠,这是一个很大的胜利.

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

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


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

较大的低流量缓存也是放置预取逻辑的好地方.硬件预取可以在诸如循环数组之类的事情上实现良好的性能,而不需要每一段代码都需要软件预取指令.(SW预取很长一段时间很重要,但硬件预取器比以前更聪明,所以Ulrich Drepper的优点是,每个程序员应该知道的内存对于许多用例都是过时的.)

低流量的高级别缓存可以提供延迟来执行诸如使用自适应替换策略而不是通常的LRU 等聪明的事情. 英特尔IvyBridge和更高版本的CPU可以做到这一点,以抵抗访问模式,这些访问模式对于工作集没有缓存命中,只是稍微大一点,无法容纳缓存.(例如,在同一方向上循环一些数据两次意味着它可能会在重复使用之前被逐出.)


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

另外,我写了一个单独的答案:在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已经使用了几年.文字大多来自昂纳雾的microarch PDF拷贝,从一些幻灯片,我发现更多的信息,更多细节上的写通过L1 + 4K组合写入高速缓存上昂纳的博客,有评论说,只有L1是WT,不L2.

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

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

  • 我读过的最好的答案之一。非常有用,谢谢! (3认同)
  • @St.Antario:是的,当然。我认为这是众所周知的事实。这就是为什么这个答案使用 L1I / L1D 但只使用 L2 或 L3。Split L1 为您提供了更多用于代码和数据获取的带宽,并且基本上是用于较大较慢的外部缓存的带宽过滤器。我从未听说过任何 CPU 使用比 L1 更远的分割缓存,甚至在 x86 之外也是如此。例如[“分割”缓存意味着什么。它有什么用处(如果有的话)?](//stackoverflow.com/q/55752699) 根据分割 L1 和统一内存层次结构的其余部分定义了“修改的哈佛”,因为*没有人*使用分割 L2。 (2认同)

归档时间:

查看次数:

21387 次

最近记录:

6 年,11 月 前