csapp---6.5高速缓存存储器

由于CPU的运行速度比主存的运行速度快很多,而且差距越来越大,系统设计者只能在CPU寄存器与主存之间插入一个小的SRAM高速缓存存储器,称为L1高速缓存(一级缓存),用来作为主存的缓冲区域,它会提前存储CPU可能访问的数据,使得CPU可以不用等待从主存中读取数据。后来因为一级缓存都不足以弥补CPU与主存之间运行速度的差距,设计者又加上了L2高速缓存和L3高速缓存,他们的速度比L1高速缓存更慢,但是存储容量比L1高速缓存更大。为了降低复杂度,我们只假设CPU与主存之间只有L1高速缓存。

通用的高速缓存存储器组织结构

我们假设一个存储器的地址有m位,那么该存储器就有M=2^m个不同的地址。这个存储器又被分为了S=2^s个高速缓存组,每个组又被分为了E个高速缓冲行。每个行包含了三个部分,第一部分是一个用来指明该行是否包含有效数据的有效位,第二个部分是一个B=2^b字节的数据块,第三部分就是t=m-(b+s)个标记位,这些标记位是这个块的唯一标识,也就是这个块的名称。

一般来说,我们可以用元组(S, E , B, m)来描述一个高速缓存。我们一般说高速缓存的大小/容量C是指该高速缓存中所有块的大小,而不包括标记位和有效位,因此,C=B*E*S。

当一条指令要求CPU从一个m位的主存地址A中读取一个字时,它会首先将地址A发送给高速缓存。高速缓存如果知道自己保存着主存地址A处的数据字副本,它就会直接把这个数据字交给CPU。问题是高速缓存如何知道自己是否保存着主存地址A处的字副本呢?

首先,如图b所示,地址A被分为了Tag, Set index和Block offset三个部分。在查找缓存时,我们首先根据Set index找到对应的组,我们知道我们有2^s个组,而Set index有s位,我们将s位的set index翻译成十进制无符号数i,然后找到这2^s个组第i个组,这个组就是有可能保存了目标副本的组。然后我们再查找这个组中每一行的标志位,逐一比较它们与地址中的tag是否一样,如果没找到一样的,就说明数据肯定没放在这个缓冲中了。如果找到一样的了,我们再检查这个行的有效位是不是1,如果不是1就说明该行的块没有保存有效数据,那就说明查找失败,不命中了。如果tag一样,有效位又是1,就说明该地址的值肯定在缓存中。但是因为一个块有B=2^b个字节这么大,我们还需要定位具体这个数据在包中的哪个位置,因此我们就使用Block offset作为目标数组在这个块的地址偏置,最后我们就找到这个数据了。

扩展思考:

通过了解在缓存查找这个过程,我们也可以知道主存中的地址是如何映射到缓存中的,比如缓存每次会把主存中B=2^b个字节的连续数据放一个块里,并且块中第一个数据的block offset肯定是0,最后一个数据的block offset肯定是B-1,Set index相同的块会被缓存放在同一组中,缓存最多能同时保留E个相同Set index的块,但毫无疑问E个是远远不够保留主存中所有相同Set index的数据的,因为主存的物理地址有2^m这么大,而E<<2^t。

下面给出了上述各个参数的汇总:

6.4.2 直接映射高速缓存

根据每个组的高速缓存行数E,我们将高速缓存分为了不同的类型。每个组只有一行(E=1)的高速缓存称为直接映射高速缓存。

如果缓存命中,缓存就会将数据交给CPU。如果缓存没包含CPU要的数据,那么就是缓存不命中,此时一级缓存会从主存中复制一个包含目标数据的块,在复制过程中,CPU只能选择等待。当复制完成了,缓存会把块放到一个合适的行中,然后从这个块中抽取出目标数据,将它返回给CPU,CPU又开始工作。

缓存确定自身是否包含CPU想要的数据(存放在主存地址w),然后抽取出目标数据的过程,被我们分为了三步:1)组选择;2)行匹配;3)字抽取。接下来我们将展示直接映射高速缓存在这三步中是如何操作的。

1.组选择

在组选择中,我们将会根据主存地址w中的set index选择相应的组。

2.行匹配

在直接映射高速缓存中,因为每一个组中只有一行,我们只需要比较该行的有效位是否为1,该行的标志位是否与地址中的tag是否相同即可。如果满足这两个条件就说明该地址的数据确实在该行中,如果不满足就肯定是缓存不命中。

3.字选择

如果行匹配了,那么地址中的块偏移位block offset就告诉了缓存,目标数据的第一个字节在块中的位置。我们可以把块看作一个字节的数组,而block offset就是这个数组的二进制索引。

问题是如果缓存中不存在CPU需要的地址中的数据,缓存将会怎么做呢?

4.行替换

当缓存不命中时,它需要从主存中取出一个包含了目标地址的数据的块,然后将这个块按照它主存地址中的set index确定要放在缓存中的哪个组,因为直接映射高速缓存每个组只有一行,如果这一行已经有一个有效的数据块了,那么我们就丢弃这个数据块,用我们从主存中取出的块代替它。

5.冲突不命中

冲突不命中一种不命中的类型,它是因为我们交替地访问映射到缓存中同一个组的内存块。这样做的话,即使我们有足够的高速缓存空间,也会导致每次使用都会缓存不命中,需要从内存中复制数据块到内存。缓存反复地加载和驱逐相同的块被称为抖动。一个避免抖动的简单方法是在内存中填充一些块,这样使得原本映射到同一个组的内存块因为set index改变不能映射到同一个组。

旁注:

为什么我们要用中间的位而不是高位来做组索引?

因为如果用高位来做组索引,内存中连续的块就会因为具有相同的地址高位,从而映射到同一个组中。如果一个程序具有良好的空间局部性,会接连访问连续的数据,而每次访问相邻数据都需要加载和驱逐相同的组,高速缓存中的其余组都是空的,在这种情况下,高速缓存的利用率很低。因此,我们要用中间的位而不是高位来做组索引。

6.4.3 组相联高速缓存

组相联高速缓存被定义为一个1<E<C/B的高速缓存。E=1即每一个组只有一行,E=C/B即每一个缓存只有一个组。E=2的组相联高速缓存一般被称为2路组相联高速缓存。

1.组选择

组相联的组选择与直接映射高速缓存一样。

2.行匹配与字选择

我们将组相联高速缓存中的每个组看成是一个map,其中key值是地址的有效位和标记位,value就是块的内容。然后根据地址的block offset从块中取出相应的内容。因为组相联高速缓存的一个重要思想就是组中每一行都可以包含任何映射到这个组的内存块,所以我们必须搜索组中的每一行,来找到想要的行,再从这一行中的块中根据block offset找到想要的数据。

3.不命中时的行替换

如果缓存不命中的时候,缓存必须从内存中取出块,并将其放入缓存中相应组中的行,但是问题是如果此时相应组中每一行都有数据,我们该怎么办?我们将会驱逐一行,将取出的内存块放进去。关于选择哪一行驱逐有多种策略,比如LFU会选择某一段时间访问次数最少的那一行驱逐,LRU会选择最久没被人访问过的那一行驱逐。

6.4.4 全相连高速缓存

全相联高速缓存即一个缓存只有一个组,这个组包含了缓存中所有的行。

1.组选择

只有一个组,所以地址中没有组索引位,地址只被划分位标记和块偏移

2.行匹配和字选择

全相连高速缓存的行匹配与字选择策略与组相联高速缓存一样。

6.4.5 有关写的问题

假设我们要写一个字数据到内存的一个地址上,这里有两种情况:一种是缓存中保存了该内存地址的数据,即缓存写命中,另一种是缓存中没有保存该内存数据,即缓存写不命中。

我们先讨论缓存写命中的情况,即我们要写一个地址已经被缓存了的数据到内存中。首先,毫无疑问,我们会先更新高速缓存中代表该地址的数据。然后,我们需要更新内存中该地址的数据,这里有两种方法去更新内存。第一种方法,我们称为直写,这种方法会立即将缓存更新后块写入内存中。这种方法虽然简单,但是每次写都会引起总线流量的浪费,因为一个块中大部分数据是不需要更新的。另一种方法叫写回。这种方法将会尽可能地推迟更新内存数据,只有当替换算法要取组这个更新过的块时,才会把它写到内存中去。由于程序具有局部性,缓存总是会访问更新后的内存数据,根本不需要访问内存,更可能缓存还没有将更新后的数据写入相应的内存地址,该内存地址的数据又需要被更新了,后一次更新覆盖了前一次更新。因此这种方法就可以显著推迟更新时间,从而减少总线流量,但是它的缺点是增加了复杂性。高速缓存必须为每个行维护一个额外的修改位,表示这个高速缓存块中的是否被修改过,而且还没有写入内存。

另一种情况是缓存写不命中的情况。我们也有两种方法处理这种情况。一种方法是写分配,它会首先加载内存中的块到高速缓存中,再更新这个高速缓存块,它通常与写回高速缓存配合,这样子就不需要马上再更新内存。它的问题是每次不命中都需要从内存中读取块,依然浪费了总线流量。另一种方法是非写分配,它会直接跳过缓存,直接将数据写入内存,这个方法通常会与直写搭配,写回方法与它配合不能完全地发挥写回的优点。

我们建议程序员在写程序时都预设计算机采用了写回与写分配的高速缓存模型。因为随着逻辑电路密度的提高,写回的高复杂性已经不成为阻碍了,而且写回与写分配的方式与我们读缓存的方式是对称的,即优先读取或写入缓存,尽可能避免访问内存,以提高速度。

6.4.6 一个真实的高速缓存层次结构的解剖

现实中的高速缓存既可以保存数据,又可以保存指令。只保存指令的高速缓存被称为i-cache,只保存程序数据的高速缓存被称为d-cache,既保存指令,又保存数据的高速缓存被称为unified cache。现代处理器包括独立的i-cache和d-cache。这样做最重要的原因是CPU可以同时读一个指令字和一个数据字。而且我们可以使用不同的访问模式来优化这两个高速缓存,它们可以有不同的块大小(B),相联度(E)和容量(C)。使用不同的高速缓存也可以避免出现连续读取指令与数据时出现冲突不命中的情况,不过因为独立缓存的容量较小,出现容量不命中的可能性会增加。

图中是一个Intel Core i7处理器的高速缓存层次结构。每个芯片有四个核,每个核有自己的L1 i-cache, L1 d-cache和L2 unified cache。所有核共享L3 unified cache。这些缓存都在CPU芯片上。

6.4.7 高速缓存参数的性能影响

有许多指标来衡量高速缓存的性能

不命中率:不命中数量/引用数量

命中率:1-不命中率

命中时间:从高速缓存传输一个字到CPU所需的时间

不命中处罚:由于不命中所需要的额外的时间。

影响高速缓存性能的几个参数

1.高速缓存大小的影响

一方面,较大的高速缓存会提高命中率,另一方面,大的高速缓存运行得会慢一些,这会增加命中时间。

2.块大小的影响

较大的块更可以利用程序的空间局部性,提高命中率。然而,相同高速缓存大小,越大的块意味着行数越少,代表能保存的相同set index的块数量越少,缓存中离散的地址越少,这意味对于时间局部性比空间局部性强的程序的命中率可能会降低。较大的块对于不命中处罚的处罚也越高,因为块越大,传送时间就越长。

3.相联度的影响

较高的相联度,代表一个组中的行数越多。这降低了出现冲突不命中的可能性,因为新来的块有更多的块可供替代。不过,实现较高的相联度的成本就比较高,因为要实现更多的标志位和控制逻辑。而且较高的相联度也会增加命中时间,因为要遍历的行更多了,还会增加不命中处罚,因为要遍历更多的行才能选择出要驱逐的行。

因此,相联度的选择需要考虑命中时间与不命中处罚。我们一般为L1选择较低的相联度,因为我们希望L1高速缓存的命中时间尽可能地短,而其不命中处罚已经非常低了,高一点点也无所谓。对于更低层的缓存,我们就会选择更高的相联度,以降低不命中的可能性与减少不命中处罚。

4.写回与直写的影响

写回策略可以降低总线传送的次数,因此对于下层的缓存,因为它们一次传送的块会很大,它们越可能采用写回策略。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值