Cache(二):回归cache自身

前一篇学习了前辈的几个名词概念,下面回归cache本身看看。

大部分的几乎全部的内容来自前辈文章:https://www.pudn.com/news/634f67b52aaf6043c94071b6.html

1、CPU访问内存的瓶颈

程序的局部性原理(原文栗子很有意思)

  • 时间局部性 :指的是同一个内存位置,从时间维度来看,它能够在较短时间内被多次引用。
  • 空间局部性 :指的是同一个内存位置,从空间维度来看,它附近的内存位置能够被引用 。

在这里插入图片描述
可以看到,传统方式下,内存控制器放在主板的北桥中,这种方式下延迟必然比现代方式里将内存控制器集成到CPU内部要大。当然,即便是在现代的CPU中放内存控制器,CPU内部的吞吐量也是DDR带宽的数倍。另外,如果CPU有多颗核,每颗核都有访问内存的需求,这也会造成竞争导致吞吐量下降。

CPU和内存之间的吞吐量差异很大,我们把CPU看成是闪电侠,内存看成是乌龟。现在有一项任务,闪电侠和乌龟要互相配合将气球夹在他们中间,然后走向终点。这时你就会发现一个尴尬的事情,为了保证气球不落地,闪电侠即使再快,也得等乌龟的脚步。因此你会看到闪电侠急得想骂娘,但也只能慢慢跟着乌龟的速度走。对于经常需要对内存数据进行操作的CPU,也是类似的情况,CPU要读写内存的时候,内存就是大爷,它啥时候回应了CPU,CPU才能继续干活。

为了解决CPU和内存之间的瓶颈,cache出现了。(描述很到位)

2、Cache如何利用局部性原理提升

在最开始接触cache时,就一直有疑问,内存那么大,cache那么小,这个怎么能起到加速作用呢?这就要提到最开始说的局部性原理了。假设所有程序员写程序,都按照局部性原理保证的最差的方式来写,那么cache能起到的作用也确实不会有这么大了。

cache中缓存了内存的部分数据。当CPU要访问内存时,首先会查找cache,如果cache里存在对应内存地址的数据(cache hit),则直接从cache中取;否则发生cache miss,若没有则需要从内存中读取数据,并同时把这块数据放入cache中。对于写数据场景,情况也类似,但具体情况要根据cache的写执行策略相关。

cache由于制作工艺和材料等要求较高,成本比内存大多了,因此,容量不可能造的太大。根据局部性原理,cache其实也能够使用较小的容量提升相当大的性能,局部性较好的程序,一定时间内所用的数据并不多并且较为集中。(局部性-空间集中、时间集中)

但cache的加入也对程序员们提出了更高的要求,如果写的程序空间和时间局部性都较差的话,那么cache miss就是家常便饭了,这样的程序即使使用cache,对性能也提升不了多少。另外,在多核系统中,cache还带来了一个麻烦的事情,就是cache一致性的问题,为了解决这个问题,又出现了cache一致性协议。下面我们先来看看cache的结构。

2.1 直接映射方式的cache结构(Direct mapped cache)

假设我们有一个cache,它的cache line为16字节,cache大小总共128字节。cache内部总共分为128 /16 = 8块(blocks)。下图是8 个 cache line存储实际数据的空间,B表示Byte:

在这里插入图片描述
这块空间我们称为data array,光有这块缓存内存数据的空间,我们是没有办法使用cache的。我们还需要一些其它的辅助信息来帮助实现cache的访问功能。

下面我们来看直接映射方式的cache是如何工作的。

    假设CPU要访问0x015a(bit[15:0]: b'0000,0001,0101,1010)这个地址(假设地址位宽16 bits)。

首先,我们要知道这个地址具体要访问哪一个byte,假设cache命中,那么这个byte肯定会在某个cache line中,并且这个byte肯定是这个cache line里的某个byte。由于cache line是16个字节,因此最低4个bit就能索引到具体对应那个byte。最低的4个bit ([b3:b0]) 我们称为offset,用来索引cache line中的一个字节,。

然后,我们要知道用的是哪个cache line,cache line总共有8个,因此用3个bit就能索引到是哪个cache line。这样我们就是用[b6:b4]这个3个bit作为cache line的索引,我们称为index。

接下来,还要解决一个有无的问题,这个要访问的地址,cache上到底有没有将缓存对应地址的内容呢?这个问题我们可以通过增加一块表来辅助查找,这个表要能通过[b15:b7]这剩下的9个bit来进行查找,这个表还要给出cache line是否是有效的状态(valid)。剩下的9个bit我们称为tag,这个辅助的查找表我们称为tag array。

(嘤嘤嘤,终于在今天知道这个cache在什么位置了。)

最后,如果CPU修改过cache的内容,cache的内容和内存(或下一级cache)上不一致,那么cache line中也要能有相应状态记录,我们称这种状态为dirty。

直接映射方式的cache主要结构我们就有了:

(tag的内容很丰富:既有cache的内容,也有cacheLine是否有效。)
在这里插入图片描述

需要注意一点,图中展示的是逻辑结构,并不代表实际硬件tag array和data array是分开做的。

从图中可以看出,tag array具体有多少条数据,和cache line的数量是一一对应的。CPU访问0x015a时,可以知道,tag = 0x2, index = 0x5, offset = 0xa。首先根据index,我们就知道了这个index对应哪个cache line(下标为5),然后找到cachel line对应的tag entry(下标也是5),根据tag entry里的内容来对比tag的值,以及V bit。

如果tag entry里记录的内容是地址高9bit的值(tag = 0x2),并且valid bit(V)为1,则表示cache line中缓存的是对应地址的内存数据。那么最后CPU就根据offset值去data array里去取出offset对应的Byte(B10)。

假设系统中内存地址范围是从0x0000到0x017F(总共0x180个字节,128 * 3),那么直接映射Cache方案,内存内容和cache line对应关系如下:

在这里插入图片描述
上图左边是RAM,每128字节为一个block,每个block可以看做和data array中的cache line组织结构一样。可以理解为我们以cache line为大小去划分了RAM, 每128字节划分8个小块(index 0 - 7),每个小块的内容如果要被缓存到cache,就直接对应到data array里相同下标的cache line。

直接映射Cache的优缺点:

直接映射Cache的优点是硬件实现简单,成本更低。但是缺点也很明显,我们可以看到0x0000, 0x0080, 0x0100开始的地址,用的cache line是同一个(index为0)。假设有程序短时间内会频繁地访问0x0000, 0x0080, 0x0100的内容,那么就会发生尴尬的事情。初始状态下,所有tag array里的V位都为0。CPU先访问0x0000,发生cache miss,然后cache将0x0000开始的16个字节读取到第1个cache line(index = 0)。接着程序将使用0x0080的数据,此时cache会将第1个cache line的内容无效化,重新从内存中读取16个字节到第1个cache line。接下来程序使用0x0100的数据,同样的,第1个cache line的内容再次被替换成了0x0100开始的16个字节。这样一来,cache基本等于没用了。这种情况我们称为cache颠簸(cache thrashing)。
(归根结底不够用)

为了解决这个问题,出现了多路组相连cache。

2.2、 两路组相连cache(Two-way set associative cache)

为了简单起见,我们用最简单的两路组相连cache作为例子。还是和前面用的cache例子一样,128字节大小,cache line为16字节。

两路组相连cache,会将data array分为两份。每份4条cache line,相应的tag array也会被分成两份。其中组(set)和路(way)的概念,参考下图:

在这里插入图片描述
从上图可以看出,一个组(Set)中有两个cache line和两个tag entry。一路(Way)中包含4条cache line。从左到右(仅从图示的逻辑角度看,并且实际物理结构)的两个tag entry和两个cache line组成了一个Set;从上到下的4个cache line组成了一个Way。

由于两个cache line分到了一组,因此总共有4个Set,此时我们只需要2 bits作为index就可以索引到组。并且index相同的时候,可以存放两个tag值,如图中的0x87和0x5,对应index相同的两个地址。在直接映射方案中,如果index = 1, 那么cache line 1所对应的tag只可能有一个值。在两路组相连cache中,由于index 1对应的cache line有两个,因此同一个index下可以对应连个不同的tag值。此时index也被称为set index,表示用来取哪一个set。接下来就是根据set里的tag array来找到我们该使用哪一个way。

两路组相连cache和直接映射cache最大的区别是:直接映射缓存一个地址只对应一个cache line,而两路组相连cache可以对应到两个cache line。

两路组相连cache的优缺点

回到之前直接映射cache的cache thrashing问题。对于两路组向量cache,假设程序访问的地址顺序是0x0000, 0x0040, 0x0080(由于两个cache line对应同一个index,因此地址相比于直接映射,循环重复index的地址间隔小了一半)。当访问0x0000时,可以从set 0 中拿出一路cache line存放这个地址开始的内容,假设是Way 0。当访问0x0040时,可以从set 0中拿出另一路cache line存放这个地址开始的内容,假设是Way 1。当访问0x0080时,这时,我们才要考虑替换出去一个cache line,假设我们硬件采用的是LRU策略,Way 0相比于Way 1使用的更少,那么就将Way 0(0x0000)对应的cache line替换成0x0080的内容。

可以看到,同一个index存放多个cache line,可以减少cache thrashing的发生频率。如果增加way的数量,则冲突发生的概率会进一步减少。

可以看到,直接映射缓存是组相连缓存的一种特殊情况,每个组只有一个cache line。因此,直接映射缓存也可以称作单路组相连缓存。

2.3、全相连映射cache(Full associative cache)

理解了组相连cache的结构,再来看全相连cache就比较好理解了。全相连映射cache中只有一个set,因此我们就不需要index字段了。对于任意地址(cache line size对齐的),它都可以映射到任意一个cache line上。其结构如下图所示:

在这里插入图片描述
全相连映射的cache,能够最大程度上避免cache thrashing。但是硬件实现的难度和成本相对较高。

https://blog.csdn.net/vivo01/article/details/127243849

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值