【Arch】MSHR in Non-Blocking Cache

  1. Cache Hit/Miss的几种策略

(为了简化流程,只考虑L1 Data Cache)

Read

Write

Hit

  • 正常读Cache

  • Write Through

写Cache -> 写内存

  • Write Back

写Cache & dirty bit置1 ->(再次被写)写内存

Miss

  • Read Through

直接读内存

  • Read Allocate

读内存->写Cache ->读Cache

  • Write Allocate

读内存->写Cache & 合并写数据->写内存

  • Write No-Allocate

直接写内存

表1

  1. 为什么需要Non-Blocking Cache

首先应该明确,Cache的主要设计思想是为了尽量减少访存时间,而大多数情况下直接访问内存的延时要远大于访问Cache,应当尽量减少直接读写内存,因此一种常见的Cache策略是Write Back(Hit)+ Read/Write Allocate(Miss),以下的讨论都在此假设的基础上展开。

从表1可以看出,一旦发生Cache Miss,对内存的一次访问是无法避免的,如果在访问内存期间又发生了新的 Cache Miss,为了保证程序的正确性,可以有以下2种处理方式:

  • Blocking Cache:最简单的办法是在Cache Miss未被解决之前,停止其他的Load/Store指令执行,这种阻塞会严重影响并行度,降低了处理器的性能。

【Arch】MSHR in Non-Blocking Cache_第1张图片

图1:Blocking Cache

  • Non-Blocking Cache:允许在Cache Miss未解决时继续执行Load/Store指令,这样做能够将一部分访问内存的时间隐藏起来,与Blocking Cache相比显著提升性能,前提是后续的指令与发生Miss的指令没有数据依赖,并且需要额外的硬件资源将所有的Miss信息缓存起来,以保证Load/Store指令的执行顺序。

【Arch】MSHR in Non-Blocking Cache_第2张图片

图2:Non-Blocking Cache

  1. 司机与乘客:MSHR和Load/Store Table

如前述,发生Cache Miss后都需要从内存中读回整个数据块,Read Miss需要将这个数据块写入到Cache,Write Miss则需要先将这个数据块和写数据合并,然后将合并后的数据块写入到Cache,因此要实现Non-Blocking的Miss处理方式,需要一个存储单元将所有Miss信息保存起来,这个单元就被称为MSHR(Miss Status/information Holding Register),由本体和Load/Store Table组成,如下图。

【Arch】MSHR in Non-Blocking Cache_第3张图片

图3:MSHR

为了解释MSHR的工作原理,首先定义以下两个概念:

  • Primary Miss:首次缺失,访问某个地址时发生的首次Cache Miss。

  • Secondary Miss:再次缺失,某个地址在首次缺失尚未解决时再次被访问,包括第2~N次Cache Miss,直到访问内存结束,返回了这个地址的数据块。

此外还应该明确,只要后续的访问地址与首次缺失地址落在同一个Cache Line,都属于再次缺失,因为这一系列缺失只能通过读回同一个Cache Line来解决。

3.1 MSHR本体

MSHR本体保存首次缺失的信息,包括以下三项:

  • Valid:表示当前entry是否被占用,发生首次缺失时置1;当前entry对应的Cache Line从内存读回时,释放表项,Valid Bit清零。

  • Block Address:表示Cache Line中数据块的地址,以32bit物理地址和64B数据块为例,需要6bit才能定位到某个字节,那么Block Address就需要32 - 6 = 26bit;对于每次Cache Miss,都需要比较Block Address才能知道是否为首次缺失,如果是就在MSHR本体中占用一项,否则就与首次缺失的entry合并为一项,等待对应的Cache Line读回。

  • Issued:表示当前entry对应的Cache Line是否已经在读回的过程中,因为访问内存的请求并不一定能立即发出,所以需要此项标记。

3.2 Load/Store Table

Load/Store Table记录所有的Cache Miss信息,包括以下五项内容:

  • Valid:表示当前entry是否被占用,所有Cache Miss都会占用一个表项。

  • MSHR entry:表示当前entry从属于MSHR本体中的哪一个表项,每当一个Cache Line被读回,首先在MSHR本体中比较Block Address,然后就可以在Load/Store Table中找到哪些Cache Miss可以被解决,因此Load/Store Table的表项是顺序写入的,但会同时释放一部分。

  • Dest Reg:对于Read Miss,这部分记录目的寄存器号,当所需的数据块被读回时就可以将其送往对应的寄存器中;对于Write Miss,这部分记录写数据在Store Buffer(专用于保存写数据的额外存储单元)中的位置,当所需的数据块被读回时就与Store Buffer中对应数据块合并,然后写入Cache,同时释放Store Buffer当中的一项。

  • Type:表示当前entry对应Miss的指令类型,例如MIPS指令集当中的LW(Load Word)、LH(Load Half Word)、SW(Store Word)等,当Cache Miss被解决时根据这部分来正确地执行指令的后续部分。

  • Offset:表示当前entry对应数据在数据块中的位置,根据这部分可定位到字节。

3.3 工作流程

通过MSHR本体和Load/Store Table的配合,能够支持Non-Blocking的Cache Miss处理方式。

  • 首先查找MSHR本体,将Cache Miss的地址和所有Block  Address进行比较:

  • 如果有Match,说明发生的是再次缺失,只需要将当前的Miss信息写到Load/Store Table;

  • 如果无Match,说明发生的是首次缺失,此时需要同时写MSHR本体和Load/Store Table;

  • 如果MSHR本体或Load/Store Table已满,应当暂停流水线,等待这两部分都有空闲的表项再继续执行上述流程。

回到本小节的标题,MSHR的两部分可类比于正常运行中的公交车,1个司机(MSHR本体的表项)对应1到N个乘客(Load/Store Table的表项),始发站(首次缺失)司机上车,中间站(再次缺失)乘客逐渐增多,终点站(Cache Line读回)司机和乘客全部下车。

  1. 引入的代价是什么

如前述,Non-Block Cache可以在处理Cache Miss的过程中提高处理器的性能,其代价体现在时序、面积和功耗上,可能包含以下两部分:

  • 专用存储单元的硬件开销

  • 比较电路和控制电路

一种优化的思路是in-Cache MSHR,也就是将发生Cache Miss的Cache Line直接用作MSHR本体,这需要在每个Cache Line加上1bit的Transient位,也就是MSHR本体的Issued位;Tag部分用于保存Block Address,其他部分保存MSHR的其他信息。

这样做可以节省一部分的硬件资源,但缺点也相当明显,那就是MSHR的读写会受限于Cache的端口宽度,可能会导致耗费多个周期才能从Cache中拿到MSHR信息,势必会降低性能,也就是出现了硬件设计中无处不在的“时间空间tradeoff”的情况。

  1. 未完待续

千里之行,始于足下。

开新坑&下集预告:《Clifford异步系列:fifo/clk/reset》,品品“异步仙人”的经典设计。

参考文献

《超标量处理器设计》,姚永斌编著,清华大学出版社

《一文告诉你怎么解决cache miss的问题》,知乎moon,https://zhuanlan.zhihu.com/p/72590268

你可能感兴趣的:(体系结构拾遗,系统架构,硬件架构)