CPU的cache工作原理

CPU的cache工作原理

博主微信:flm13724054952,不懂的有疑惑的也可以加微信咨询,欢迎大家前来指教共同探讨,谢谢!博主最近的工作是CPU集成设计,所以接下来的篇章将以CPU的学习讲解为主。最后再打个小广告,欢迎各位对数字IC设计感兴趣的博友来我们公司“众星微”,可以内推哦。

1 about Cache

在思考CPU的架构为什么需要cache之前,我们首先来了解一下:CPU是如何运行软件程序的。
我们应该知道程序是运行在 RAM之中,RAM 就是我们常说的DDR(L3: DDR、Flash等)。我们称之为main memory(主存)。当我们需要运行一个进程的时候,首先会从磁盘设备(L4,CF card、UFS、SSD等)中将可执行程序load到主存中,然后开始执行。在CPU内部存在一堆的通用寄存器(register)。 如果CPU需要将一个变量(假设地址是A)加1,一般分为以下3个步骤:(在没有cache的情况下)
(1) CPU 从主存中读取地址A的数据到内部通用寄存器 R0;(一个cycle)
(2) 通用寄存器 R0 加1;(一个cycle)
(3) CPU 将通用寄存器 x0 的值写入主存。(几十个cycle)
CPU系统里面cache与memory的层次结构流程图会如下图所示, 如果有cache作为主存与CPU之间的缓存数据地带,一方面变量不需要频繁写到主存先缓存在cache里面,那么指令的执行时间将大大加快。另一方面CPU可以不用长时间占用总线的访问。Cache是存在于主存与CPU之间的一级存储器, 由静态存储芯片(SRAM)组成,容量比较小但速度比主存高得多, 接近于CPU的速度。 Cache的功能是用来存放那些近期需要运行的指令与数据。目的是提高CPU对存储器的访问速度。 当CPU试图从主存中load/store数据的时候, CPU会首先从cache中查找对应地址的数据是否缓存在cache 中。如果其数据缓存在cache中,直接从cache中拿到数据并返回给CPU。
在这里插入图片描述

2 cache architecture

在传统的CPU计算机架构里面,cache架构目前分为两大架构诺依曼架构跟哈维架构。在诺依曼架构里面,一个cache同时用于指令与数据,是统一的一个Cache。在哈维架构里面,指令与数据总线是分开的,所以同时出现两个Cache,I-Cache跟D-Cache。在arm的CPU系统里面,会有不同的指令与数据一级缓存,由统一的L2-Cache支持。
如下图所示,在Cache的架构里面,I-Cache由data_ram跟tag_ram组成;D-Cache由data_ram,tag_ram,dirty_ram组成。
Cache line:CPU一次性从主缓存搬移数据位宽的能力,如R5的cache line为32byte;
Cache tag:指示cache的数据对应来自主缓存的哪个位置,同个cache line的tag是一样的;
Cache index:指示将主缓存的数据编译到Cache的哪一行,决定了cache的size大小;
Cache way:将Cache均分成几块MEM来存储,每一块都称为ways;
Cache hit:CPU在cache寻找数据时,如果tag与主存tag一致那就是缓存命中;
Cache miss:CPU在cache寻找数据时,如果tag与主存tag不一致那就是缓存缺失;
在这里插入图片描述data ram
在Cache的架构里面,data_ram就是对软件呈现的缓存数据的地方。
tag ram
在Cache的架构里面,tag_ram是告诉cache(data_ram)的数据是来自与主缓存的对应位置。tag_ram里面还有一个valid bit来指示当前对应的cache_line的数据是否是有效的。
dirty ram
在Cache的架构里面,dirty ram仅仅是D-Cache所拥有,其作用是在指示当前D-Cache的数据是否与主缓存一致,一致的话dirty为1,不一致的话dirty为0。
cache的容量
cache的容量计算如下:
(1)用cache line跟ways来计算容量:例如R5的cache line为32个字节,D-Cache有4ways,(一个ways由两个数据位宽32bits 数据深度1024lines的bank拼接在一起);所以cache容量占据32×4×1024×2÷8=32KB;
(2)用下图数据位宽,深度,数据块数来计算:所以cache容量占据32×1024×8÷8=32KB;
在这里插入图片描述一个cache line一般还含有ag字段和valid位,通常情况下我们都是用cache中数据部分占的空间表示cache的容量,也就是32字节,但是实际上,它还额外多占用了tag ram跟dirty ram的存储空间。

3 Cache maping

Cache的映射原则是根据cache index来定位主缓存的数据应该搬移到cache的哪一行,然后把对应物理地址的tag信息及地址对应数据放入对应的tag ram跟data ram。并把相关的valid位置1。后面当CPU要在cache寻找数据的时候就可以根据cache line的tag字段来辨识当前的cache line数据是那个block的。
如下图所示,Cache的映射方式有三种,直接映射(direct-mapping),组相连映射(set-associative-mapping),全相连映射(full-associative-mapping)。
在这里插入图片描述

3.1 直接映射(direct-mapping)

直接映射缓存是cache只有一个ways,搬移数据直接就在同一块上缓存;所以在硬件设计上会更加简单,因此成本上也会较低。但是也会引入其他问题,如下图所示,根据直接映射缓存的工作方式,我们可以看到,地址0x00-0x3f地址处对应的数据可以覆盖整个cache。0x40-0x7f地址的数据也同样是覆盖整个cache。
在这里插入图片描述我们现在思考一个问题,如果一个程序试图依次访问地址0x00、0x40、0x80,cache中的数据会发生什么呢?首先我们应该明白0x00、0x40、0x80地址中index部分是一样的。因此,这3个地址对应的cache line是同一个。所以,当我们访问0x00地址时,cache会缺失,然后数据会从主存中加载到cache中第0行cache line。当我们访问0x40地址时,依然索引到cache中第0行cache line,由于此时cache line中存储的是地址0x00地址对应的数据,所以此时依然会cache缺失。然后从主存中加载0x40地址数据到第一行cache line中。同理,继续访问0x80地址,依然会cache缺失。这就相当于每次访问数据都要从主存中读取,所以cache的存在并没有对性能有什么提升。访问0x40地址时,就会把0x00地址缓存的数据替换。这种现象叫做cache颠簸(cache thrashing)。针对这个问题,我们引入多路组相连缓存。我们首先研究下最简单的两路组相连缓存的工作原理。

3.2 组相连映射(set-associative-mapping)

两路组相连缓存的硬件成本相对于直接映射缓存更高。因为其每次比较tag的时候需要比较多个cache line对应的tag(某些硬件可能还会做并行比较,增加比较速度,这就增加了硬件设计复杂度)。为什么我们还需要两路组相连缓存呢?因为其可以有助于降低cache颠簸可能性。那么是如何降低的呢?
如下图所示:根据两路组相连缓存的工作方式,两路组相连缓存较直接映射缓存最大的差异就是:第一个地址对应的数据可以对应2个cache line,而直接映射缓存一个地址只对应一个cache line。那么这究竟有什么好处呢?
在这里插入图片描述我们依然考虑直接映射缓存一节的问题“如果一个程序试图依次访问地址0x00、0x40、0x80,cache中的数据会发生什么呢?”。现在0x00地址的数据可以被加载到way 1,0x40可以被加载到way 0。这样是不是就在一定程度上避免了直接映射缓存的尴尬境地呢?在两路组相连缓存的情况下,0x00和0x40地址的数据都缓存在cache中。试想一下,如果我们是4路组相连缓存,后面继续访问0x80,也可能被被缓存。
因此,当cache size一定的情况下,组相连缓存对性能的提升最差情况下也和直接映射缓存一样,在大部分情况下组相连缓存效果比直接映射缓存好。同时,其降低了cache颠簸的频率。从某种程度上来说,直接映射缓存是组相连缓存的一种特殊情况,每个组只有一个cache line而已。因此,直接映射缓存也可以称作单路组相连缓存。

3.3 全相连映射(full-associative-mapping)

既然组相连缓存那么好,如果所有的cache line都在一个组内。岂不是性能更好。其实并没有完美的cache-mapping,例如全相连缓存是将所有的cache line都放置在一个组内,主缓存的任何数据可以放在任何一个cache line里面。虽然这可以最大程度的降低cache颠簸的频率。但是硬件(设计的复杂性)成本上也是更高。所以设计这样子的一个全相连的Cache是不切合实际的,除非这个cache非常之小。实际在应用上来说对于L1 Cache来说当超过4 ways以上的性能的提升已经是最小的了。
如下图所示CPU最常用的组相连映射方案:对于一个4ways 32KB的D-Cache,cache line是8字。那么1 ways就会有256 lines。当cache line=256时,那么cache index=8;另外还需要3bit去指示cache line中哪一个字是有效的。
在这里插入图片描述

4 Cache controller

缓存控制器是一个管理维护cache的硬件逻辑电路,对软件程序而言是不可见的。他可以自动把数据从主缓存写到cache里面,还可以响应来自CPU core的读写请求并执行相应的操作去Cache或者主缓存。
当CPU core发起读请求的时候,他会检测这个地址是否在cache里面。这个行为叫做查表(cache look-up)。然后通过检验cache line的tag是否与查找地址tag一致来决定cache是否命中。当cache hit而且cache line是有效的,那么读操作将在cache发生。
当CPU core请求的指令或者数据来自一个特殊的地址(对应的tag不在cache里面),那么就会发生缓存丢失(cache miss)。当发生了cache miss那么这个请求将会出去外面的主缓存响应(要么L2 Cache要么external memory)。他也会导致发生了cache linefill,及在请求主缓存的相应数据到CPU core去时,也复制一份到cache里面来。但是这些行为对于你来说是不可见的。CPU core在使用数据之前不必等待行填充完成。缓存控制器通常会首先访问缓存线中的关键字。例如,如果一个load指令在缓存中缺失并触发了一个cache linefill,那么第一次读到外部内存就是load指令提供的实际地址。这些关键数据被提供给处理器管道,而高速缓存硬件和外部总线接口然后在后台读取高速缓存线的其余部分。

5 Cache policies

cache policies里面有三种策略allocate policy,replacement policy,write policy来影响cache的操作。

5.1 allocate policy

read allocate policy
只在读取时分配cache线路。如果内核执行了缓存中未执行的写操作,那么缓存不会受到影响,写操作将进入层次结构的下一层。
write allocate policy
读或写在cache中失败的情况分配一条cache line。因此,更准确地说,这可以称为读写缓存分配策略。对于缓存中未执行的内存读操作和缓存中未执行的内存写操作,都会执行缓存行填充操作。这通常与当前ARM核心上的回写策略结合使用。

5.2 replacement policy

replacement policy是在当cache发生cache miss的时候,缓存控制器需要将数据从主缓存搬移到cache的策略。当数据从主缓存搬移到cache而选择一个cahce line的行为叫做victim(牺牲)。但是选择cache line的某一行是不固定随机的,如果选择的那一行包含了有用的数据而被我们替换掉新数据的话,那么会对后续访问造成影响,那么就需要把选中的这一行cache line数据搬移到主缓存保留,这种行为就叫做逐出(eviction)。
就如上面所说,replacement policy就是如何选择cache line来做victim。大部分的处理器支持两种替换策略Round-robin or cyclic replacement policy跟Pseudo-random replacement policy。
1、Round-robin or cyclic replacement policy
就是一个victim-counter,在一个有效的cache ways里面做周期循环计数,当计数超过ways的最大行数就归0,重新计数,这样子来选择victim cache line。
2、Pseudo-random replacement policy
就是一个伪随机替换Pseudo-random-victim counter,通过伪随机函数来随机选择cache的某一行去做victim cache line。
总的来说 循环替代政策通常更具可预测性,但某些用例中的性能可能会受到不利影响。因此,伪随机策略通常是首选。

5.3 write policy

write policy的策略分为了两种策略write-throughwrite-back
1、CPU向cache写入数据时的操作,两者的区别:
Write-through:CPU向cache写入数据时,同时向memory(后端存储)也写一份,使cache和memory的数据保持一致;
Write-back:CPU更新cache时,只是把更新的cache区标记一下,并不同步更新memory(后端存储)。只是在cache区要被新进入的数据取代时,才更新memory(后端存储)。
2、两者相比较优势跟缺点:
Write-through:优点是简单,但是每次都要访问memory,速度比较慢;
Write-back:优点是CPU执行的效率提高,缺点是实现起来技术比较复杂。
系统设计人员应评估最适合其应用程序的缓存操作策略要求。如果使用了回写策略,那么缓存通常必须在在切换上下文以保持内存系统的一致性之前描述的。这个切换前可能需要大量写入CP15寄存器。或者,选择直写策略可以降低系统性能并增加功耗每个缓存操作都保持一致性,这有时是不必要的。然而,这意味着缓存正在不断地被清理,因此缓存维护通常是花更少的时间。

6 write and fetch buffer

write buffer是一个位于CPU内部的硬件逻辑块。当CPU执行一条store指令,数据往往会先写到writer buffer中去,然后CPU不等他真正写到主缓存就去执行下一条指令 。但是writer buffer过一段时间会主动把数据写到主缓存之中去。
因此writer buffer的优点就是可以大大提供CPU执行指令的能力,提高整体性能,但是当CPU大量往writerbuffer写数据且速度比write buffer写到主缓存的速度快,那么writer buffer很快就会被写满,那么这个时候对CPU的性能提升就不是很明显了。而且当软件同事需要CPU想获取当前的数据再去执行下一条指令的时候,那么这个时候writer buffer反而也是碍事了。
一些write buffer还有一个写合并的功能,对相连的单发写数据会合并在一起,通过brust的方式写到主缓存去,这样子可以提高数据流的传输速度。但是相反来说,当软件想要的是单字节操作数据不是连续一段数据,那么这个时候就是反而碍事了。
在某些系统中,称为fetch buffer的类似组件可以用于读取。特别是,CPU core通常包含预取缓冲区,在指令实际插入到管道之前从内存中读取指令。通常,这样的缓冲区对你来说是透明的。在研究内存排序规则时,我们也应该考虑与此相关的一些可能的危险。

参考文献

<<arm-v7架构手册>>

猜你喜欢

转载自blog.csdn.net/weixin_39015789/article/details/115442238
今日推荐