cache介绍

原帖地址: http://www.wowotech.net/memory_management/458.html?from=timelinehtml


今天探究的主题是cache,咱们围绕几个问题展开。为何须要cache?如何判断一个数据在cache中是否命中?cache的种类有哪些,区别是什么? 算法

为何须要cache memory

      在思考cache是什么以前咱们首先先来思考第一个问题:咱们的程序是如何运行起来的?咱们应该知道程序是运行在 RAM之中,RAM 就是咱们常说的DDR(例如 DDR三、DDR4等)。咱们称之为main memory(主存)当咱们须要运行一个进程的时候,首先会从Flash设备(例如,eMMC、UFS等)中将可执行程序load到main memory中,而后开始执行。在CPU内部存在一堆的通用寄存器(register)。若是CPU须要将一个变量(假设地址是A)加1,通常分为如下3个步骤: 数组

  1. CPU 从主存中读取地址A的数据到内部通用寄存器 x0(ARM64架构的通用寄存器之一)。
  2. 通用寄存器 x0 加1。
  3. CPU 将通用寄存器 x0 的值写入主存。

咱们将这个过程能够表示以下:缓存

image

      其实现实中,CPU通用寄存器的速度和主存之间存在着太大的差别。二者之间的速度大体以下关系: 架构

image

      CPU register的速度通常小于1ns,主存的速度通常是65ns左右。速度差别近百倍。所以,上面举例的3个步骤中,步骤1和步骤3实际上速度很慢。当CPU试图从主存中load/store 操做时,因为主存的速度限制,CPU不得不等待这漫长的65ns时间。若是咱们能够提高主存的速度,那么系统将会得到很大的性能提高。现在的DDR存储设备,动不动就是几个GB,容量很大。若是咱们采用更快材料制做更快速度的主存,而且拥有几乎差很少的容量。其成本将会大幅度上升。咱们试图提高主存的速度和容量,又指望其成本很低,这就有点难为人了。所以,咱们有一种折中的方法,那就是制做一块速度极快可是容量极小的存储设备。那么其成本也不会过高。这块存储设备咱们称之为cache memory。在硬件上,咱们将cache放置在CPU和主存之间,做为主存数据的缓存。 当CPU试图从主存中load/store数据的时候, CPU会首先从cache中查找对应地址的数据是否缓存在cache 中。若是其数据缓存在cache中,直接从cache中拿到数据并返回给CPU。当存在cache的时候,以上程序如何运行的例子的流程将会变成以下: app

image

CPU和主存之间直接数据传输的方式转变成CPU和cache之间直接数据传输。cache负责和主存之间数据传输。 性能

多级cache memory

      cahe的速度在必定程度上一样影响着系统的性能。通常状况cache的速度能够达到1ns,几乎能够和CPU寄存器速度媲美。可是,这就知足人们对性能的追求了吗?并无。当cache中没有缓存咱们想要的数据的时候,依然须要漫长的等待从主存中load数据。为了进一步提高性能,引入多级cache。前面提到的cache,称之为L1 cache(第一级cache)。咱们在L1 cache 后面链接L2 cache,在L2 cache 和主存之间链接L3 cache。等级越高,速度越慢,容量越大。可是速度相比较主存而言,依然很快。不一样等级cache速度之间关系以下:大数据

image

      通过3级cache的缓冲,各级cache和主存之间的速度最萌差也逐级减少。在一个真实的系统上,各级cache之间硬件上是如何关联的呢?咱们看下Cortex-A53架构上各级cache之间的硬件抽象框图以下: .net

image

      在Cortex-A53架构上,L1 cache分为单独的instruction cache(ICache)和data cache(DCache)。L1 cache是CPU私有的,每一个CPU都有一个L1 cache。一个cluster 内的全部CPU共享一个L2 cache,L2 cache不区分指令和数据,均可以缓存。全部cluster之间共享L3 cache。L3 cache经过总线和主存相连。 设计

多级cache之间的配合工做

      首先引入两个名词概念,命中和缺失。 CPU要访问的数据在cache中有缓存,称为“命中” (hit),反之则称为“缺失” (miss)。多级cache之间是如何配合工做的呢?咱们假设如今考虑的系统只有两级cache。

image

      当CPU试图从某地址load数据时,首先从L1 cache中查询是否命中,若是命中则把数据返回给CPU。若是L1 cache缺失,则继续从L2 cache中查找。当L2 cache命中时,数据会返回给L1 cache以及CPU。若是L2 cache也缺失,很不幸,咱们须要从主存中load数据,将数据返回给L2 cache、L1 cache及CPU。这种多级cache的工做方式称之为inclusive cache。某一地址的数据可能存在多级缓存中。与inclusive cache对应的是exclusive cache,这种cache保证某一地址的数据缓存只会存在于多级cache其中一级。也就是说,任意地址的数据不可能同时在L1和L2 cache中缓存。

直接映射缓存(Direct mapped cache)

      咱们继续引入一些cache相关的名词。cache的大小称之为cache size,表明cache能够缓存最大数据的大小。咱们将cache平均分红相等的不少块,每个块大小称之为cache line,其大小是cache line size。例如一个64 Bytes大小的cache。若是咱们将64 Bytes平均分红64块,那么cache line就是1字节,总共64行cache line。若是咱们将64 Bytes平均分红8块,那么cache line就是8字节,总共8行cache line。如今的硬件设计中,通常cache line的大小是4-128 Byts。为何没有1 byte呢?缘由咱们后面讨论。

这里有一点须要注意,cache line是cache和主存之间数据传输的最小单位。什么意思呢?当CPU试图load一个字节数据的时候,若是cache缺失,那么cache控制器会从主存中一次性的load cache line大小的数据到cache中。例如,cache line大小是8字节。CPU即便读取一个byte,在cache缺失后,cache会从主存中load 8字节填充整个cache line。又是由于什么呢?后面说完就懂了。

      咱们假设下面的讲解都是针对64 Bytes大小的cache,而且cache line大小是8字节。咱们能够相似把这块cache想一想成一个数组,数组总共8个元素,每一个元素大小是8字节。就像下图这样。

image

      如今咱们考虑一个问题,CPU从0x0654地址读取一个字节,cache控制器是如何判断数据是否在cache中命中呢?cache大小相对于主存来讲,可谓是小巫见大巫。因此cache确定是只能缓存主存中极小一部分数据。咱们如何根据地址在有限大小的cache中查找数据呢?如今硬件采起的作法是对地址进行散列(能够理解成地址取模操做)。咱们接下来看看是如何作到的?

image

      咱们一共有8行cache line,cache line大小是8 Bytes。因此咱们能够利用地址低3 bits(如上图地址蓝色部分)用来寻址8 bytes中某一字节,咱们称这部分bit组合为offset。同理,8行cache line,为了覆盖全部行。咱们须要3 bits(如上图地址黄色部分)查找某一行,这部分地址部分称之为index。如今咱们知道,若是两个不一样的地址,其地址的bit3-bit5若是彻底同样的话,那么这两个地址通过硬件散列以后都会找到同一个cache line。因此,当咱们找到cache line以后,只表明咱们访问的地址对应的数据可能存在这个cache line中,可是也有多是其余地址对应的数据。因此,咱们又引入tag array区域,tag array和data array一一对应。每个cache line都对应惟一一个tag,tag中保存的是整个地址位宽去除index和offset使用的bit剩余部分(如上图地址绿色部分)。tag、index和offset三者组合就能够惟一肯定一个地址了。所以,当咱们根据地址中index位找到cache line后,取出当前cache line对应的tag,而后和地址中的tag进行比较,若是相等,这说明cache命中。若是不相等,说明当前cache line存储的是其余地址的数据,这就是cache缺失。在上述图中,咱们看到tag的值是0x19,和地址中的tag部分相等,所以在本次访问会命中。因为tag的引入,所以解答了咱们以前的一个疑问“为何硬件cache line不作成一个字节?”。这样会致使硬件成本的上升,由于本来8个字节对应一个tag,如今须要8个tag,占用了不少内存。

咱们能够从图中看到tag旁边还有一个valid bit,这个bit用来表示cache line中数据是否有效(例如:1表明有效;0表明无效)。当系统刚启动时,cache中的数据都应该是无效的,由于尚未缓存任何数据。cache控制器能够根据valid bit确认当前cache line数据是否有效。因此,上述比较tag确认cache line是否命中以前还会检查valid bit是否有效。只有在有效的状况下,比较tag才有意义。若是无效,直接断定cache缺失。

上面的例子中,cache size是64 Bytes而且cache line size是8 bytes。offset、index和tag分别使用3 bits、3 bits和42 bits(假设地址宽度是48 bits)。咱们如今再看一个例子:512 Bytes cache size,64 Bytes cache line size。根据以前的地址划分方法,offset、index和tag分别使用6 bits、3 bits和39 bits。以下图所示。

image

直接映射缓存的优缺点

直接映射缓存在硬件设计上会更加简单,所以成本上也会较低。根据直接映射缓存的工做方式,咱们能够画出主存地址0x00-0x88地址对应的cache分布图。

image

      咱们能够看到,地址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)。针对这个问题,咱们引入多路组相连缓存。咱们首先研究下最简单的两路组相连缓存的工做原理。

两路组相连缓存(Two-way set associative cache)

      咱们依然假设64 Bytes cache size,cache line size是8 Bytes。什么是路(way)的概念。咱们将cache平均分红多份,每一份就是一路。所以,两路组相连缓存就是将cache平均分红2份,每份32 Bytes。以下图所示。

image

      cache被分红2路,每路包含4行cache line。咱们将全部索引同样的cache line组合在一块儿称之为组。例如,上图中一个组有两个cache line,总共4个组。咱们依然假设从地址0x0654地址读取一个字节数据。因为cache line size是8 Bytes,所以offset须要3 bits,这和以前直接映射缓存同样。不同的地方是index,在两路组相连缓存中,index只须要2 bits,由于一路只有4行cache line。上面的例子根据index找到第2行cache line(从0开始计算),第2行对应2个cache line,分别对应way 0和way 1。所以index也能够称做set index(组索引)。先根据index找到set,而后将组内的全部cache line对应的tag取出来和地址中的tag部分对比,若是其中一个相等就意味着命中。

所以,两路组相连缓存较直接映射缓存最大的差别就是:第一个地址对应的数据能够对应2个cache line,而直接映射缓存一个地址只对应一个cache line。那么这究竟有什么好处呢?

两路组相连缓存优缺点

      两路组相连缓存的硬件成本相对于直接映射缓存更高。由于其每次比较tag的时候须要比较多个cache line对应的tag(某些硬件可能还会作并行比较,增长比较速度,这就增长了硬件设计复杂度)。为何咱们还须要两路组相连缓存呢?由于其能够有助于下降cache颠簸可能性。那么是如何下降的呢?根据两路组相连缓存的工做方式,咱们能够画出主存地址0x00-0x4f地址对应的cache分布图。

image


      咱们依然考虑直接映射缓存一节的问题“若是一个程序试图依次访问地址0x00、0x40、0x80,cache中的数据会发生什么呢?”。如今0x00地址的数据能够被加载到way 1,0x40能够被加载到way 0。这样是否是就在必定程度上避免了直接映射缓存的尴尬境地呢?在两路组相连缓存的状况下,0x00和0x40地址的数据都缓存在cache中。试想一下,若是咱们是4路组相连缓存,后面继续访问0x80,也可能被被缓存。

      所以,当cache size必定的状况下,组相连缓存对性能的提高最差状况下也和直接映射缓存同样,在大部分状况下组相连缓存效果比直接映射缓存好。同时,其下降了cache颠簸的频率。从某种程度上来讲,直接映射缓存是组相连缓存的一种特殊状况,每一个组只有一个cache line而已。所以,直接映射缓存也能够称做单路组相连缓存。

全相连缓存(Full associative cache)

既然组相连缓存那么好,若是全部的cache line都在一个组内。岂不是性能更好。是的,这种缓存就是全相连缓存。咱们依然以64 Byts大小cache为例说明。

image

      因为全部的cache line都在一个组内,所以地址中不须要set index部分。由于,只有一个组让你选择,间接来讲就是你没得选。咱们根据地址中的tag部分和全部的cache line对应的tag进行比较(硬件上可能并行比较也可能串行比较)。哪一个tag比较相等,就意味着命中某个cache line。所以,在全相连缓存中,任意地址的数据能够缓存在任意的cache line中。因此,这能够最大程度的下降cache颠簸的频率。可是硬件成本上也是更高。

一个四路组相连缓存实例问题

考虑这么一个问题,32 KB大小4路组相连cache,cache line大小是32 Bytes。请思考一下问题:

1). 多少个组? 2). 假设地址宽度是48 bits,index、offset以及tag分别占用几个bit?

总共4路,所以每路大小是8 KB。cache line size是32 Bytes,所以一共有256组(8 KB / 32 Bytes)。因为cache line size是32 Bytes,因此offset须要5位。一共256组,因此index须要8位,剩下的就是tag部分,占用35位。这个cache能够绘制下图表示。

image


Cache分配策略(Cache allocation policy)

cache的分配策略是指咱们什么状况下应该为数据分配cache line。cache分配策略分为读和写两种状况。

  1. 读分配(read allocation):

    当CPU读数据时,发生cache缺失,这种状况下都会分配一个cache line缓存从主存读取的数据。默认状况下,cache都支持读分配。

  2. 写分配(write allocation):

    当CPU写数据发生cache缺失时,才会考虑写分配策略。当咱们不支持写分配的状况下,写指令只会更新主存数据,而后就结束了。当支持写分配的时候,咱们首先从主存中加载数据到cache line中(至关于先作个读分配动做),而后会更新cache line中的数据。

Cache更新策略(Cache update policy)

cache更新策略是指当发生cache命中时,写操做应该如何更新数据。cache更新策略分红两种:写直通和回写。

  1. 写直通(write through):

    当CPU执行store指令并在cache命中时,咱们更新cache中的数据而且更新主存中的数据。cache和主存的数据始终保持一致。

image

2. 写回(write back):

       当CPU执行store指令并在cache命中时,咱们只更新cache中的数据。而且每一个cache line中会有一个bit位记录数据是否被修改过,称之为dirty bit(翻翻前面的图片,cache line旁边有一个D就是dirty bit)。咱们会将dirty bit置位。主存中的数据只会在cache line被替换或者显示flush操做时更新。所以,主存中的数据多是未修改的数据,而修改的数据躺在cache line中。

       同时,为何cache line大小是cache控制器和主存之间数据传输的最小单位呢?这也是由于每一个cache line只有一个dirty bit。这一个dirty bit表明着整个cache line时候被修改的状态。

image

实例 :

     假设咱们有一个64 Bytes大小直接映射缓存,cache line大小是8 Bytes,采用写分配和写回机制。当CPU从地址0x2a读取一个字节,cache中的数据将会如何变化呢?假设当前cache状态以下图所示。


image

      根据index找到对应的cache line,对应的tag部分valid bit是合法的,可是tag的值不相等,所以发生缺失。此时咱们须要从地址0x28地址加载8字节数据到该cache line中。可是,咱们发现当前cache line的dirty bit置位。所以,cache line里面的数据不能被简单的丢弃,因为采用写回机制,因此咱们须要将cache中的数据0x11223344写到地址0x0128地址(这个地址根据tag中的值及所处的cache line行计算获得)。这个过程以下图所示。

image

当写回操做完成,咱们将主存中0x28地址开始的8个字节加载到该cache line中,并清除dirty bit。而后根据offset找到0x52返回给CPU。

cache替换策略

1、最不常用算法(Least Frequently Used,LFU)

LFU算法认为:应该将一段时间内被访问次数最少的那行数据替换出。

实现方法:每行设置一个计数器。新行创建后从0开始计数,每被访问一次,被访问行的计数器增1。当须要替换时,对这些特定行的计数值进行比较,将计数值最小的行换出,同时将这些特定行的计数器清零。

该算法不能严格反映近期访问状况。


2、近期最少使用算法(Least Recently Used,LRU)

LFU算法将近期内长久未被访问过的行换出。

实现方法:每行设置一个计数器,但它们是cache每命中一次,命中行计数器清零,其余各行计数器增1。当须要替换时,将计数值最大的行换出。

该算法保护了刚拷贝到cache中的新数据行,较为符合cache的工做原理,有效地提升了命中率。


3、随机替换

不须要算法,从特定的行位置中随机地选取一行换出便可。

该策略在硬件上容易实现,速度较前两种策略快。可是会在必定程度上下降cache的效率。这些不足会随着cache容量增大而减少。

相关文章
相关标签/搜索