五一小长假过完了,在刷了四天剧后又要开启975养老生活了,收假第一天补一篇关于CPU高速缓存的文章,错误之处欢迎你们指正。数组
咱们都知道,程序是由一条条指令和数据组成的,CPU在运行时的工做也是周而复始的执行一条条用途各异的指令。
在一开始,程序加载到主存,在程序执行的过程当中,将指令一条条的从主存中取出并执行,宏观上来讲咱们把主存看作是一个很大的一维字节数组,地址便可看做为数组的下标。这样的结构能够确保程序能够顺利执行。但是存在一个问题,主存的读取速度太慢,而CPU的运算速度却很是快,CPU执行完一条指令后又等待十分漫长的时间才能等到下一条指令的到来,CPU的资源被严重浪费。缓存
其实CPU一开始执行指令的速度也并非特别快的,经过不断的优化将其速度进行提高。过了一段时间,CPU执行指令的速度愈来愈快了,这时发现影响程序执行快慢的瓶颈不在于CPU了,而在于CPU执行指令与主存读取指令的速度差距上,因此必需要想办法让读取指令的速度快起来,这便出现了咱们的高速缓存。性能优化
引入高速缓存后的存储器层次结构图以下所示,固然这只是一个简化图,利于咱们理解整个存储器的结构层次。其中L1,L2就是咱们所说的高速缓存, 这个结构相似于一个金字塔,越往上则越靠近CPU,读取和写入的速度越快,造价也越昂贵。性能
这里咱们假设L1与L2之间的缓存块大小为8KB,L2与主存之间的缓存块大小为64KB为例来阐述一下CPU读取指令整个流程,寻址器首先会到L1高速缓存中去寻找指令,若是没有CPU则等待寻址器到L2高速缓存中寻找指令,若是L2高速缓存也没有寻找到,那就从主存中寻找指令。寻找到指令后将命中的缓存块(64KB)的全部数据移动到L2,并将L2对应的缓存块(8KB)全部数据移动到L1。最终在L1中将对应的单条指令返回给CPU。优化
为什么咱们要提出缓存块这个概念?
咱们的程序指令每每是连续的,程序访问到某个数据时,那么它和它周围的数据会有很大可能在短期内被再次访问,这被称之为局部性原理。因此咱们在访问到某个数据时,索性将它和它周围的数据也提到上层的高速缓存中,下一次就能够直接从高速缓存中命中数据。3d
上节中简单描述了CPU取指令时数据在存储器结构中的流动状况,咱们说若是L1中没有咱们访问的数据则会到L2中去寻找,咱们称这个为缓存不命中,那么如何判断缓存是否命中呢?咱们将图2的高速缓存进行放大,观摩一下它的结构cdn
若是说你在疑惑t、b、s的含义,先不用管它,继续往下看天然就知道了blog
若是说CPU如今要访问某个地址为Adress的数据,寻址器将地址进行以下划分 索引
根据组索引位定位到组资源
查看缓存块有效位是否为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进行写入,那咱们存在两种方案
一、摒弃高速缓存,直接写主存
二、写入高速缓存
若是咱们选择第一种状况,那么直接写入就ok,可是就回到了一开始的问题了,写入的速度太慢,cpu要漫长的等待。那么咱们固然是采用第二种状况。先将数据读入高速缓存,再对缓存进行写入。那么这种状况咱们须要注意一个问题:不一样层级的缓存同步问题,也就是说当这个缓存块发生缓存冲突,在数据覆盖时须要将这个缓存块刷新到它的下一级高速缓存中。
经过上面的讲述我想高速缓存工做的原理应该在脑海中已经有一个流程图了。那么知道了缓存的工做原理在实际工做中有什么用呢?这个就回到了咱们一开始说的局部性原理了,因为计算机老是将一段连续的地址进行缓存(缓存块),因此若是咱们编写的代码符合局部性原理,那么运行效率将会有很大的提高,这为咱们的程序性能优化提供了一个方面的指引。
举个例子 若是遍历一个长度相同数组和一个链表,因为数组在物理存储上是连续的,遍历数组时效率会更快