超标量处理器设计学习合集cache篇之cache的一般设计


前言

在超标量处理器中,有2部分直接影响性能:分支预测和cache。了解cache的基本结构及cache一致性对CPU设计者来说是必不可少的。本篇笔记就从cache 概念入手,介绍关于cache 一般设计的相关知识,本合集会持续更新CPU设计相关知识,为CPU设计者提供参考。


一、为什么需要cache?

cache之所以存在,是因为在计算机的世界中,存在如下2个现象。
1)时间相关性:如果一个数据现在被访问了,那么在以后很可能还会被访问。
2)空间相关性:如果一个数据现在被访问了,那么它周围的数据在以后有可能也会被访问。

cache 的出现本质上是为了解决存储器和处理器速度之间的巨大差距。

图1-1 处理器中的各种cache
处理器中的各种cache
现代的处理器多为哈弗架构,为了增加流水线的执行效率,L1 cache 一般都包括2个物理存在,指令cache(I-cache)和数据cache(D-cache),本质上来说,他们的原理是一样的,不同的是D-cache需要考虑读写,而I-cache只用考虑读取。L1 cache的核心目标是求快,速度与处理器相当,一般使用SRAM实现;而L2 cache是求全,并且指令和数据共享,以MB为单位,有可能是多核共享的。

二、cache设计目标

1. L1 cache

L1 cache最靠近处理器,它是流水线的一部分,需要和处理器保持近似相等的速度,对于L1 cache 来说,“快" 就是硬道理。

I-cache 需要每周期读取多条指令,但是即使延迟很大也不会造成处理器性能的下降,除非遇到预测跳转的分支指令,这些延迟才会对性能造成影响。

D-cache 需要支持在每周期内有多条load/store指令的访问,也就是需要多端口的设计,而D-cache本身的容量很大,当采用多端口设计时,占用硅片面积很难接受,也会导致过大的延迟,这个延迟会直接暴露给流水线中后续的指令,考虑到load 指令一般处于相关性的顶端,会对处理器的性能造成负面影响。

2. L2 cache

相比于L1 cache的求“快”,L2 cache则是求“全”。一般情况下,L2 cache 都是指令和数据共享,它的主要功能是为了可以尽量保存更多的内容,在现代的处理器中,L2 cache都是以MB 为单位的。而且,在多核的环境中,L2 cache 有可能是多核之间共享的。由于它被访问的频率不是很高(L1 cache的命中率是比较高的),所以并不需要多端口的设计,它的延迟也不是特别重要,因为只有在L1 cache miss时才会被访问。但是L2 cache 需要有比较高的命中率,因为在它发生缺失时需要访问物理内存(一般是DRAM),这个访问时间会很长,因此要尽可能提高L2 cache的命中率

三、cache的结构

cache 主要由Tag 和Data 两部分组成。cache是利用了程序中的时空相关性,一个被访问的数据,它本身和它周围的数据最近都有可能被访问。

Data 用来保存一片连续地址的数据,而Tag 用来存储这片连续数据的公共地址。一个Tag和它对应的所有数据组成的一行称为一个cache line,而cache line 中的数据部分称为数据块,如果一个数据可以存储在cache中的多个地方,这些被同一个地址找到的多个cache line 称为cache set。

如图1-2 所示,为cache的一种简单结构,可以清楚看到各名称的定义。
图1-2  cache的结构
cache 只能保存最近被处理器使用过的内容,由于它的容量有限,很多情况下要找的指令或者数据并不在cache中,这称为cache的缺失(cache miss),它发生的频率直接影响着处理器的性能,在计算机领域,影响cache缺失的情况可以概括如下。
(1)compulsory,由于cache只是缓存以前访问过的内容,因此,第一次被访问的指令或数据肯定不在cache中,这样看起来,这个缺失是肯定发生的。当然可以采取预取的方法来尽量降低这种缺失发生的概率。
(2)capcity,cache 容量是影响cache缺失发生频率的一个关键因素,当程序频繁使用的5个数据属于不同的cache set,而cache 的容量只有4个cache set时,那么就会经常发生缺失了。
(3)conflict,主要是由于相连度的因素,在一个有着两路结构的cache中,如果程序频繁使用的三个数据属于同一个cache set时,就会发生缺失,可以使用victim cache 来缓解。

以上为影响cache 缺失的三个条件,称为3C定理,尽管在超标量处理器中,可以使用预取和victim cache 的方法,但是cache miss 无法消除。

四、cache的组成方式

在实际中,cache 有三种实现方式:直接映射(direct-mapped)cache,组相连(set-associative))cache 和全相连(fully-associative)cache 三种方式。如果cache 中只有一个数据可以容纳它,它就是直接映射的cache;如果是多个地方可以容纳它,那就是组相连的cache;如果cache中的任何地方都可以放置这个数据,那么它就是全相连的cache。也就是直接映射和全相连这两种结构的cache 实际上是组相连cache的两种特殊情况。如图3-1为cache的三种组成方式。

图3-1  cache的三种组成方式

现代处理器中的cache一般属于上述3种方式中的一个。例如TLB 和Victim cache多采用全相连结构,而普通的I-cache 和D-cache则采用组相连结构等。

1. 直接映射

直接映射结构是最容易实现的一种方式,处理器访问存储器的地址会被分为三部分:tag、index和block offest,如图4-2所示,使用index 来从cache中找到一个对应的cache line,tag用来和地址中的tag进行比较,只有他们相等时,才表明这个cache line是想要的那个。在一个cache line中有很多数据,通过存储器地址中的block offset 找到真正想要的数据,可以定位到每个字节。在cache line中还有一个有效位(valid),用来指示数据是否有效。只有在之前被访问过的存储器地址,它的数据才会存在于对应的cache line中,相应的有效位也会被置为1。
图4-2 直接映射cache 结构
直接映射结构cache的一个缺点就是相同index 会寻址到同一个cache line,导致两个index相同的存储器交互地访问cache时,会一直导致cache缺失,严重降低处理器的执行效率。
直接映射cache在实现上是最简单的,都不需要替换算法,但是执行效率很低,目前使用很少。

如图4-3 为一个32位的存储器地址,因为block offset 为5,所以data block 的大小是32字节;index是6位,表示cache中共有2^6 =64个cache line,(在直接映射中,cache line和cache set 是同义的),因此这个cache中可以存储的数据大小是64x32B = 2048B,即2KB。存储器地址中剩余的32-5-6=21位作为Tag值,而tag部分的大小是64x21b ≈1.3Kb;有效位(valid)占用的大小是64x1b=64b。但是一般情况下,都是以数据部分的大小表示cache的大小,因此这个cache大小为2KB。
图4-3  存储器的地址划分

2. 组相连

组相连是为了解决直接映射结构cache的不足而提出的,存储器中的一个数据不单单只能放在一个cache line中,而是可以放在多个cache line中,对于一个组相连结构的cache来说,如果一个数据可以放在n个位置,则称这个cache是n路组相连的cache(n-way set-associative cache)。如图4-4,为一个2-way 的组相连的cache。
图4-4  2-way 组相连cache
这种结构仍旧使用index寻址,此时可以得到两个cache line,这两个cache line称为一个cache set, 通过比较tag 确定哪个被hit,如果两个tag 比较结果都不相等则为 cache miss。
其优点是可以显著减少cache 缺失发生的频率,但是延迟增大,有时甚至进行流水以减少对处理器周期时间的影响,但是会导致load指令的延迟增大,从而影响处理器执行效率。

目前这种结构应用最为广泛,在实际实现时,Tag 和Data是分开放置的,称为Tag SRAM 和data SRAM,同时访问时称为并行;如果先访问Tag SRAM,根据Tag 比较的结果再去访问Data SRAM部分称为串行访问。

对于并行访问的结构,当Tag部分的某个地址被读取的同时,这个地址在Data 部分对应的数据也会被读取出来并送到一个多路选择器上,多路选择器根据Tag选出对应的Data block,然后根据存储器地址中block offset的值,选择处合适的字节,一般将悬着字节的这个过程称为数据对齐(data alignment)。
并行访问的流水线address calculation →disambiguation→cache access→result drive

而对于串行访问,首先对Tag SRAM进行访问,根据Tag 比较的结果,就可以知道数据部分中,哪一路的数据是需要被访问的,此时可以直接访问这一路的数据而不需要多路选择器,而且只需要访问数据部分指定的那个SRAM,其他不需要被访问的SRAM 可以将使能信号置为无效,这样可以节省很多功耗。
串行访问的流水线address calculation →disambiguation→Tag access→data access→result drive

并行访问有较低的时钟频率和较大的功耗,但是访问cache的时间缩短了一个周期,考虑到乱序执行的超标量处理器中,可以将访问cache的这段时间通过填充其他的指令而掩盖起来。而串行访问由于不需要多路选择器,可以降低处理器的周期时间,但是这样设计也有一个明显的缺点,那就是cache的访问增加了一个周期,导致load 指令延迟增加,因为load指令处于相关性的顶端,这样会对处理器的执行效率造成一定的负面影响。

综上,对于超标量处理器来说,当cache的访问处于处理器的关键路径上时,可以使用串行访问的方式来提高时钟频率;而对于普通的顺序执行的处理器来说,由于无法对指令进行调度,使用并行访问比较合适

3. 全相连

在全相连的方式中,对于一个存储器地址来说,它的数据可以放在任意一个cache line中。如图4-5所示。存储器地址中将不再有index部分,而是直接在整个cache中进行tag 比较,找到比较结果相等的那个cache line;这种方式相当于直接使用存储器的内容来寻址,也就是内容寻址的存储器(CAM)。实际当中的处理器在使用全相连结构的cache时,都是使用CAM来存储Tag值,使用普通的SRAM 来存储数据的。当CAM 中的某一行被寻址到时,SRAM 中对应的行(word line)也将会被找到。
图4-5 全相连结构

五. cache的写入

1. I-cache的写入

一般在RISCv中,I-cache 都不会被直接写入内容的,即使有自修改的情况发生,也并不是直接写I-cache,而是要借助D-cache来实现,将要改写的指令作为数据写到D-cache中,然后将D-cache中的内容写到下级存储器中(例如L2 cache ,这个存储器一定是内指令和数据共享的,这个过程称为clean),并将I-cache中的所有内容置为无效,这样处理器再次执行时,就会使用哪些被修改的指令了。

2. D-cache的写入

对于D-cache来说,它的写操作和读操作有所不同。这个时候就需要考虑一致性问题了。

在对cache进行写入(store)时,最简单的方式就是当数据在写到D-cache的同时,也写到它的下级存储器中,这种写入方式称为写通(write-through),由于D-cache的下级存储器需要的访问时间相对是比较长的,而store 指令在程序中出现的频率比较高,所以这种方式效率不会很高。

如果在执行store指令时,数据被写到D-cache中,只是将被写入的cache line 标记为dirty,并不将这个数据写到更下级的存储器中,只有当cache中这个被标记的line要被替换时,才将它写到下级寄存器中,这种方式就称为写回(write-back),这种方式可以减少写慢速存储器的频率,从而获得比较好的性能。缺点是造成D-cache 和下级存储器中有很多地址中的数据不一致,给存储器的一致性管理带来负担。

以上所讲的是要写入的地址总是在D-cache中存在,而实际中,有可能这个地址并不在D-cache中,这就发生了缺失,此时最简单的方式就是将数据直接写到下级存储器中,而并不写到D-cache中,这种方式称为Non-Write allocate。与之对应的方法是write allocate,在这种方法中,如果写cache时发生了缺失,会首先从下级存储器中将这个发生缺失的地址对应的数据块取出来,将要写入到D-cache中的数据合并到这个数据块中,然后将这个被修改过的数据块写到D-cache中。

综上,对于D-cache来说,一般情况下,写通的方法总是配合Non-Write allocate一起使用的,他们都是直接将数据更新到下级存储器中。如图5-1,为两种方法配合的工作流程图。
图 5-1 write Through 和non-Write allocate 两种方法配合工作的流程图
同样地,在D-cache中,写回(Write back)的方法和write allocate也是配合在一起使用**的。如图5-2,为两种方法配合工作的流程图。

从图5-2中可以看出,在D-cache中采用写回(write back)的方法时,不管是读取还是写入时发生缺失,都需要从D-cache 中找到一个line 来存放数据,这个被替换的line 如果是dirty状态,那么首先需要将其中的数据写回到下一级存储器中,然后才能够使用这个line 存放新的数据。图5-2  write back 和write allocate 配合工作流程图

六. cache的替换策略

不管是读取还是写入D-cache时发生了cache miss,都需要从对应的cache set中找到一个line来存放从下级存储器中读出的数据,如果此时这个cache set内所有line都已经被占用了,那么就需要替换掉其中一个,如何从这些有效的cache line 找到一个并替换之,这就是替换策略,本章介绍几种最常用的替换算法。

1. 近期最少使用法

**近期最少使用法(LRU)**会选择最近被使用次数最少的cache line,因此这个算法需要追踪每个cache line的使用情况,这需要为每个cache line 都设置一个年龄部分,每次当一个cache line被访问时,它对应的年龄部分就会增加,或者减少其他cache line的年龄值,这样当进行替换时,年龄值最小的那个cache line 就是被使用次数最少的了,会选择它进行替换。但是,随着cache 相关度的增加(也就是way 的个数的增加),要精确地实现这种LRU 的方式成本就很高了。因此实际中对于这种相关度很高的cache都是使用“伪LRU”的方法,将所有的way进行分组,每一组使用1位的年龄部分。如图6-1,为一个8路组相连结构的cache中实现“伪LRU”的示意图。
图6-1 伪LRU 算法的工作流程对于共有8个way的cache来说,共需要三级的年龄位(4→2→1),对于每一个年龄位来说,它为0时,表示编号较小的way最近没有被使用,而编号较大的way被使用过。

2. 随机替换法

在处理器中,cache的替换算法一般都是使用硬件来实现的,因此如果做得很复杂,会影响处理器的周期时间,于是就有了随机替换的实现方法,这种方法不再需要记录每个way的年龄信息,而是随机地选择一个way 进行替换,相比于LRU 替换方法来说,这种缺失率更高,但是随着cache容量的增加,这个差距是越来越小的。当然,在实际的设计中很难实现严格的随机,一般采用一种称为时钟算法的方法来实现近似的随机。其工作原理本质是一个计数器,其位宽由cache的相关度,也就是way的个数来决定。例如一个八路组相连结果的cache (8 way set-associative),则计数器的宽度需要3位。这种方法从理论上来讲,可能并不能获得最优化的结果,但是它的硬件复杂度比较低,也不会损失过多的性能,因此综合看起来是一种不错的折中方法。














  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值