洪柱森老师介绍--沪师经纪-刘建

7 个非常实用的 Shell 拿来就用脚本实例!

  返回  

深入理解处理器高速缓存的工作机制

2021/7/21 10:57:50 浏览:

目录

1 CPU高速缓存简单介绍

2 缓存如何判断哪些数据是更常用的

 3 cpu如何访问数据

3.1 数据在存储器层次之间以块为单位进行传递

4 高速缓存一致性问题

4.1 解决缓存冲突问题

4.2 高速缓存与内存的一致性问题

4.3 高速缓存一致的存储系统定义

4.4 MESI高速缓存一致性协议


1 CPU高速缓存简单介绍

CPU高速缓存机制的引入,主要是为了解决CPU越来越快的运行速度与相对较慢的主存访问速度的矛盾。CPU中的寄存器数量有限,在执行内存寻址指令时,经常需要从内存中读取指令所需的数据或是将寄存器中的数据写回内存。而CPU对内存的存取相对CPU自身的速度而言过于缓慢,在内存存取的过程中CPU只能等待,机器效率太低。

  为此,设计者在CPU与内存之间引入了高速缓存。CPU中寄存器的存储容量小,访问速度极快;内存存储容量很大,但相对寄存器而言访问速度很慢。而高速缓存的存储大小和访问速度都介于二者之间,作为一个缓冲桥梁来填补寄存器与主存间访问速度过大的差异。

  引入高速缓存后,CPU在需要访问主存中某一地址空间时,高速缓存会拦截所有对于内存的访问,并判断所需数据是否已经存在于高速缓存中。如果缓存命中,则直接将高速缓存中的数据交给CPU;如果缓存未命中,则进行常规的主存访问,获取数据交给CPU的同时也将数据存入高速缓存。但由于高速缓存容量远小于内存,因此在高速缓存已满而又需要存入新的内存映射数据时,需要通过某种算法选出一个缓存单元调度出高速缓存,进行替换。

  由于对内存中数据的访问具有局部性,使用高速缓存能够极大的提高CPU访问存储器的效率。

靠近CPU的小的、快速的高速缓存存储器是内存上一部分数据和指令的缓冲区域。主存缓存磁盘上的数据,而这些磁盘又常常作为存储在通过网络连接的其他机器的磁盘或磁带上的数据的缓冲区域。存储层次如下:

2 缓存如何判断哪些数据是更常用的

缓存上存储的数据是那些计算机认为在接下来更有可能访问到的数据,计算机如何判断哪些数据接下来更有可能用到呢?系统对数据的访问频率有两种假设:

1. 时间局部性

时间局部性假设目前访问的数据在接下来也更有可能再次访问到,所以计算机会把刚刚访问过的数据放入缓存中;

2. 空间局部性

空间局部性假设与目前访问的数据相邻的那些数据接下来也更有可能访问到,所以会把当前数据周围的数据放入缓存中;
一个编写良好的代码往往符合时间局部性和空间局部性。

 3 cpu如何访问数据

3.1 数据在存储器层次之间以块为单位进行传递

存储器层次结构的本质是,每一层存储设备都是较低一层的缓存。为了利用空间局部性,存储器上的数据都是按块划分的,每个块包含多个字节的数据。第k层的缓存包含第k+1层块的一个子集的副本。数据总是以块为传送单位在第k层和第k+1层来回复制的。在层次结构中任何一对相邻的层次之间块大小是一样的。不过不同的层次之间块大小可以不同,一般而言,越靠近底层,越倾向于使用较大的块。

当程序需要第k+1层的某个数据时,它首先在第k层的一个块中查找d,这里会出现两种情况:

1. 缓存命中

d刚好缓存在k层中,这里称之为缓存命中,该程序直接从k层读取d,根据存储器层次结构,这要比从k+1层取数据更快。

2. 缓存不命中

d没有缓存在k中,这种情况称之为缓存不命中,第k层的缓存从第k+1层中取出包含d的那个块,放在k层中,然后从k层读出d。这里涉及一个问题,即从k+1层中取出的块应如何放置在k层中,这里需要某种放置策略。可用的策略如下:

(1)随机放置,在k中随机选择一个位置进行放置,这种策略实现起来通常很昂贵,因为不好定位;

(2)分组放置,将第k+1层的某个块放置在第k层块的某个小组(子集)中;

1. 高速缓存的结构及与地址的映射关系

高速缓存使用分组放置策略,它把存储空间分为S组,每组E行,每行包含1个块,每个块的大小为B,它的结构可以用四元组(S,E,B,m)来表示,其中m代表地址位的个数。高速缓存的容量为C=S*E*B;

当访问数据时,我们唯一知道的是数据的地址。通过地址我们如何从缓存中找到数据所在的组号,块号和块中的位置呢?这里m个地址位分为了3个字段,其中高位的字段为t个标记位,它唯一的标记了组中的块,中间的字段为s个索引位,它标记了组号,最后的字段为块偏移,通过它可以访问块中具体的字节。另外缓存结构中每一行最前面还有一个有效位,它标志了这一行有没有存储块。缓存结构如下:

2. 一个简单的存储模型

下面定义一个简单的存储层次模型:

(1)cpu和内存之间只加了一层高速缓存;

(2)存储地址为4位,即内存中最多存16个字节;

4位的地址被分成了3段:最高位的1段为标记位,中间2位为索引组,最后1位为块偏移。从索引组的的位数可判断缓存被分成了4组,1位的块偏移说明块的大小为2个字节。这里设每组只有1行,这种情况称为直接映射高速缓存。从以上分段可知,缓存大小为 4*2*1=8 字节;

举例:地址0101;其中高位的0为标记位,标记组中的1个块,中间的两位10为组索引,通过组索引可知当前地址的块存在组2中,最后1位为偏移位,说明要取的字节存在块中的第2个位置。

3. 缓存过程

(1)初始状态缓存是空的,当cpu通过地址(假设为0001)要加载一个数据时,此时cpu会先查找高速缓存,通过中间的地址00找到第0组,通过缓存最前面的有效位判断缓存不命中,然后从内存中取得地址0001处的值。注意此时不会从内存中只取0001处的值,而是根据缓存的块的大小取得1个块,即2个字节,即0000和0001两处的值。然后会把这两个值存储在组索引为0,偏移量为0和1的地址处,并且把当前行的有效位设置为1,标记位设置为0,最后高速缓存返回新取出的高速缓存块[1]处的值;

(2)接下来,cpu如果要取0000处的值,此时也会先查找缓存,通过中间2位00找到缓存的组数为0,通过第1个有效位(1)和标记位(0)判断缓存命中,此时会直接返回偏移量0处的值,不需要再查找内存;

(3)如果接下来cpu要取1000处的值,此时会查找到组0,由于地址的标记位为1,和缓存组0中行的标记位(0)不符合,所以缓存不命中,cpu会从内存中取得相应的块,并把缓存组0处的数据覆盖掉,此时会把组的标识位设置为1。

(4)接下来再读地址0000处的值,此时又会发生缓存不命中,因为上面引用地址1000时,把块替换掉了。这种情况称为冲突不命中,也就是说我们有足够的高速缓存空间,但是却交替地引用映射到同一个组的块;

小结:

cpu加载机制为,通过地址先查找缓存,查找过程为,先通过地址中间的组索引位,找到组,然后根据组中行的有效位和标记位判断是否缓存命中,如果命中,则直接从缓存中取数据;如果没命中,则从内存中取出1个块,并用某种放置策略放在缓存中,然后从缓存返回值。在直接映射高速缓中,内存中的空间和缓存中的空间是一种多对一的映射关系,比如本例中地址0和地址8所指的内存空间的内容,在缓存中都存在第0组偏移量为0的空间中;他们在缓存中的区分由地址的最高位即标记位决定。

4 高速缓存一致性问题

4.1 解决缓存冲突问题

1. 什么是缓存冲突

当每个组只有一行的情况下,映射为同一组的块在缓存中将占用同一个存储空间,当反复加载位于同一组的两个块时,由于每次加载都会把先前加载的块覆盖掉,导致cpu对缓存的命中率为0,虽然此时在其他组依然有大量的缓存空间。

2.组相联高级缓存

为了降低缓存冲突,可以把每个组分为多行,每行存储一个块。cpu通过组索引查找块所在的组,然后通过有效位和标记位检查多行来判断是否缓存命中。当缓存不命中时,会从内存中加载一个块到缓存相对应的组中,当组内有空行时,会直接加载到空的行,否则缓存会使用某种替换策略来换掉组内的一行。常用的替换策略有随机替换,最近最少使用原则,即替换掉组内最长时间未使用的行。当缓存中只有1个组,组中包含所有行时,称为全相联高速缓存。

4.2 高速缓存与内存的一致性问题

  高速缓存在命中时,意味着内存和高速缓存中拥有了同一份数据的两份拷贝。CPU在执行修改内存数据的指令时如果高速缓存命中,只会修改高速缓存中的数据,此时便出现了高速缓存与内存中数据不一致的问题。

  这个不一致问题在早期单核CPU环境下似乎不是什么大问题,因为所有的内存操作都来自唯一的CPU。但即使是单核环境下,为了减轻CPU在I/O时的负载、提高I/O效率,先进的硬件设计都引入了DMA机制。DMA芯片在工作时会直接访问内存,如果高速缓存首先被CPU修改和内存不一致,就会出现DMA实际写回磁盘的内容和程序所需要写入的内容不一致的问题。

  为了更好的理解多核CPU环境下工作的MESI协议,这里先简单介绍单核环境下高速缓存被首先改写而导致cache与主存不一致问题的解决方案,简单来说有两种方法:通写法回写法

通写法(Write Through):

  即CPU在对cache写入数据时,同时也直接写入主存,这样就能使得主存和cache中的数据始终保存一致。

  通写法的优点是简单,硬件上容易实现,同时在调度缓存单元时不会有脏数据,调度速度快;缺点是每次cache写入操作时都增加了写主存的等待时间,效率较低。

回写法(Write Back):

  回写法和通写法的主要区别在于,回写法在CPU写cache时并不实时同步写主存,而是在进行调度被覆盖前整体的写回主存。如果被调度出的cache单元并没有被写入过,则直接被覆盖无需写回主存。

  回写法的优点是写入cache时无需同步主存,总体效率比通写法高。缺点是硬件实现较为复杂。

多核CPU高速缓存间的一致性问题

  随着单核主频速度的增长受到制约,CPU的发展由单核逐渐过度到了多核,目前主流的CPU都是多核心的。但随着多核CPU提高计算机并发性能的同时,也带来了一系列的问题,这其中就包括了多核CPU下的高速缓存一致性问题

  在多核CPU的架构下,通常每一个核心都拥有着自己独有的高速缓存,每个核心能并发的读写自己的高速缓存。高速缓存可以有多个,但其对应的内存数据逻辑上却只有一份,多核并发的修改其高速缓存中同一内存的映射数据就会出现高速缓存中的数据不一致的问题。如果不对多核CPU下的高速缓存并发访问施加一定的约束,那么并发程序中对共享内存数据的存取就会出现问题,并发程序的正确性将无法得到有效保障

4.3 高速缓存一致的存储系统定义

  在讨论解决高速缓存一致性问题的方法前,我们需要更精确的定义什么是高速缓存一致性。

  内存系统的一个本质特征是:一个内存系统应该能提供一组保存值的存储单元,当对一个存储单元执行读操作时,应该能返回“最近”一个对该存储单元的写操作所写入的值。在串行程序中,程序员利用内存来将程序中某一点计算出来的值,传递到该值的使用点,实际上就是利用了上述基本性质。同样,运行在单处理器上的多个进程或线程利用共享地址空间进行通信,实际上也是利用了内存系统的这个性质。

  一个读操作应返回最近的向那个位置的写操作所写的值,而不管是哪个线程写的。当所有的线程运行在同一个物理处理器上时,它们通过相同的高速缓存层次来看内存,因此在这种情况下,高速缓存不会引起问题。当在共享存储的多处理器系统上运行一个具有多个线程的程序时,希望不管这些线程是运行在同一个处理器上,还是位于不同的处理器上,程序的运行结果都是相同的。

  上面摘抄自书上的概念描述有些晦涩,我个人的理解是:按照程序指令的运行顺序,对于同一内存单元内容(变量值)能够令后面的读操作读取到之前最近写操作后的结果,保证程序逻辑序的正确性。这一顺序性的保证在单核环境下不是问题,因为所有的指令顺序都使用同一个高速缓存,但在多核多高速缓存副本的情况下运行某一程序的多个并发任务时就会出现问题,因为并没有约定多个处理器核心对同一存储单元并发操作时的全局顺序,即“最近”这一概念是模糊、不明确的。

  因此,一个高速缓存一致的存储系统其首先要满足的一个条件便是:根据一个程序的任意一次执行结果,都能够对每个内存单元的存取操作构造出逻辑上的全局串行序列(即使是多核体系下,对内存的存取逻辑上也要强制串行化)。这一全局逻辑串行序列还需要满足额外的两个条件:一是同一处理器所发出的程序内存存取指令顺序(程序逻辑序),与在全局逻辑串行序列中的先后顺序保持一致;二是每个读操作的值,返回的是在全局逻辑串行序列中最近的写操作之后的值。

  上述高速缓存一致性的定义隐含了在多核环境下的两个重要性质:一是写传播,二是写串行化

  写传播(Write Propagation):一个处理器对一个位置的所写入的值,最终对其它处理器是可见的。

  写串行化(Write Serialization):对同一内存单元的所有写操作(无论是来自一个处理器还是多个处理器)都能串行化。换句话说,所有的处理器能以相同的次序看到这些写操作。

4.4 MESI高速缓存一致性协议

  通常多核并行架构的CPU,每个核虽然都独自工作,但与外部存储器的交互依然是共用同一总线进行的。通过总线,每个核心都能够监听、接收到来自其它核心的消息通知,这一机制被称为总线侦听或是总线嗅探

基于总线侦听的写传播:

  每个核心在对自己独有的高速缓存行进行修改时,需要将修改通知送至总线进行广播。其它核心在监听到总线上来自其它核心的远程写通知时,需要查询本地高速缓存中是否存在同样内存位置的数据。如果存在,需要选择将其设置为失效状态或是更新为最新的值。

基于总线侦听的写串行化:

  总线上任意时间只能出现一个核的一个写通知消息。多个核心并发的写事件会通过总线仲裁机制将其转换为串行化的写事件序列(可以简单理解为逻辑上的一个FIFO事件队列),在每个写事件广播时,必须得到每个核心对事件的响应后,才进行下一个事件的处理,这一机制被称作总线事务

  而本文的主角MESI协议便是基于总线侦听机制,采用回写法、写传播失效策略的高速缓存一致性协议,其另一个更精确的名称是四态缓存写回无效协议。

 随着多核CPU中并发程序的不断运行,高速缓存被反复的读写,缓存内存行的状态也会在MESI这四种状态间反复变化。

  在MESI协议中,抽象出了四种会导致缓存内存行状态的变化缓存事件:本地读、本地写、远程读以及远程写。缓存事件针对的是某一内存缓存行的事件。

本地读(Local Read):

  本地读事件指的是本地核心对自己的缓存行进行读取。

本地写(Local Write):

  本地写事件指的是本地核心对自己的缓存行进行写入。

远程读(Remote Read):

  远程读事件指的是总线上的其它核心对某一内存缓存行进行了读取,当前核心监听到的事件。

  某一个核心的本地读事件,对于其他核心就是针对其对应内存缓存行的远程读事件。

远程写(Remote Write):

  远程写事件指的是总线上的其它核心对某一内存缓存行进行了写入,当前核心监听到的事件。

  某一个核心的本地写事件,对于其他核心就是针对其对应内存缓存行的远程写事件。

通过MESI协议,在强制串行化的总线事务帮助下能够始终保持一个全局高速缓存一致的稳定状态。

  MESI协议依赖总线侦听机制,在某个核心发生本地写事件时,为了保证全局只能有一份缓存数据,要求其它核对应的缓存行统统设置为Invalid无效状态。为了确保总线写事务的强一致性,发生本地写的高速缓存需要等到远端的所有核心都处理完对应的失效缓存行,返回Ack确认消息后才能继续执行下面的内存寻址指令(阻塞)。

原始MESI协议实现时的性能问题:

  1.对于进行本地写事件的核心,远端核心处理失效并进行响应确认相对处理器自身的指令执行速度来说是相当耗时的,在等待所有核心响应的过程中令处理器空转效率并不高。

  2.对于响应远程写事件的核心,在其高速缓存压力很大时,要求实时的处理失效事件也存在一定的困难,会有一定的延迟。

  不进行优化的MESI协议在实际工作中效率会非常的低下,因此CPU的设计者在实现时对MESI协议进行了一定的改良。

联系我们

如果您对我们的服务有兴趣,请及时和我们联系!

服务热线:18288888888
座机:18288888888
传真:
邮箱:888888@qq.com
地址:郑州市文化路红专路93号