谈谈CPU高速缓存

前言

五一小长假过完了,在刷了四天剧后又要开启975养老生活了,收假第一天补一篇关于CPU高速缓存的文章,错误之处欢迎大家指正。

正文

为什么需要高速缓存

我们都知道,程序是由一条条指令和数据组成的,CPU在运行时的工作也是周而复始的执行一条条用途各异的指令。
在一开始,程序加载到主存,在程序执行的过程中,将指令一条条的从主存中取出并执行,宏观上来说我们把主存看做是一个很大的一维字节数组,地址即可看作为数组的下标。这样的结构可以确保程序可以顺利执行。可是存在一个问题,主存的读取速度太慢,而CPU的运算速度却非常快,CPU执行完一条指令后又等待十分漫长的时间才能等到下一条指令的到来,CPU的资源被严重浪费。

其实CPU一开始执行指令的速度也并不是特别快的,通过不断的优化将其速度进行提升。过了一段时间,CPU执行指令的速度越来越快了,这时发现影响程序执行快慢的瓶颈不在于CPU了,而在于CPU执行指令与主存读取指令的速度差距上,所以必须要想办法让读取指令的速度快起来,这便出现了我们的高速缓存。

高速缓存的引入

引入高速缓存后的存储器层次结构图如下所示,当然这只是一个简化图,利于我们理解整个存储器的结构层次。其中L1,L2就是我们所说的高速缓存, 这个结构类似于一个金字塔,越往上则越靠近CPU,读取和写入的速度越快,造价也越昂贵。

图1
这里我们还需要提出的是一个缓存块的概念,L0中存储的是L1的缓存,L1中存储的是L2的缓存,依次类推,数据总是在各个层级之间上下移动,那么数据移动的单位我们称之为缓存块。比如说假设L0和L1的缓存块大小假设为8kB,那么传输单元会是8KB。L1高速缓存的物理结构如下图所示

图2

这里我们假设L1与L2之间的缓存块大小为8KB,L2与主存之间的缓存块大小为64KB为例来阐述一下CPU读取指令整个流程,寻址器首先会到L1高速缓存中去寻找指令,如果没有CPU则等待寻址器到L2高速缓存中寻找指令,如果L2高速缓存也没有寻找到,那就从主存中寻找指令。寻找到指令后将命中的缓存块(64KB)的所有数据移动到L2,并将L2对应的缓存块(8KB)所有数据移动到L1。最终在L1中将对应的单条指令返回给CPU。

为何我们要提出缓存块这个概念?
我们的程序指令往往是连续的,程序访问到某个数据时,那么它和它周围的数据会有很大可能在短时间内被再次访问,这被称之为局部性原理。所以我们在访问到某个数据时,索性将它和它周围的数据也提到上层的高速缓存中,下一次就可以直接从高速缓存中命中数据。

如何判断缓存命中

上节中简单描述了CPU取指令时数据在存储器结构中的流动情况,我们说如果L1中没有我们访问的数据则会到L2中去寻找,我们称这个为缓存不命中,那么如何判断缓存是否命中呢?我们将图2的高速缓存进行放大,观摩一下它的结构

图3
我们的缓存块除了真实的缓存块数据块之外,还有额外的1位有效位和t位标记位,我们假设L1高速缓存有S个这样的缓存块。(注意我们之前所说的缓存块大小是指真实的缓存块数据块大小,所以我们这里L1高速缓存的容量大小=S*B个字节)

如果说你在疑惑t、b、s的含义,先不用管它,继续往下看自然就知道了

如果说CPU现在要访问某个地址为Adress的数据,寻址器将地址进行如下划分

图4
寻址器根据如下步骤解析地址:
  • 根据组索引位定位到组

  • 查看缓存块有效位是否为1,如果是那么比对标记位,如若标记位一致,则表示该缓存命中。成功定位到缓存块

  • 根据最后b位计算出偏移地址

这里的t、b、s与图3的数据一致。假设我们是64位机器,那么Adress的长度应当等于64,即t+s+b=64

缓存不命中的两种

缓存不命中的情况存在两种,一种是缓存为空,一种是缓存冲突。前一种的发生的情况在计算机刚启动时会比较常见,这是计算机的缓存是空的,所以缓存不命中,后一种发生的原因我们举个很明显的例子,假设现在有一个寻址长度为16位的计算机,标记位t=3,索引位s=5,偏移位b=8。如果我们访问这样的两个地址:
A=121 111 00021
B=131 111 00022
会发现A,B虽然地址不同,但是根据上述的步骤,它们会映射到同一个高速缓存块,但是由于标记位不一致导致缓存不命中,这种情况称之为缓存冲突。

写数据的情况

上面的大篇幅中我们都是读数据的情况,那么如果是写数据高速缓存改如何工作呢?由于写的情况涉及到数据的修改,所以务必要比读的情况更复杂一些, 假如我们现在要对地址A进行写入,那我们存在两种方案
1、摒弃高速缓存,直接写主存
2、写入高速缓存
如果我们选择第一种情况,那么直接写入就ok,但是就回到了一开始的问题了,写入的速度太慢,cpu要漫长的等待。那么我们当然是采用第二种情况。先将数据读入高速缓存,再对缓存进行写入。那么这种情况我们需要注意一个问题:不同层级的缓存同步问题,也就是说当这个缓存块发生缓存冲突,在数据覆盖时需要将这个缓存块刷新到它的下一级高速缓存中。

小结

通过上面的讲述我想高速缓存工作的原理应该在脑海中已经有一个流程图了。那么知道了缓存的工作原理在实际工作中有什么用呢?这个就回到了我们一开始说的局部性原理了,由于计算机总是将一段连续的地址进行缓存(缓存块),所以如果我们编写的代码符合局部性原理,那么运行效率将会有很大的提升,这为我们的程序性能优化提供了一个方面的指引。

举个例子
如果遍历一个长度相同数组和一个链表,由于数组在物理存储上是连续的,遍历数组时效率会更快

猜你喜欢

转载自juejin.im/post/5cce4c016fb9a032514bbe6c