CPU缓存一致性
- 写直达
- 写回
- 缓存一致性
- 总线嗅探
- MESI协议
CPU Cache通常分为三级缓存,L1Cache,L2Cache,L3Cache,级别越低的离CPU越近,访问速度越快,但同时容量越小,价格越贵。在多核的CPU中,每个核都有各自的L1,L2Cache,而L3是所有核共享的。
让我们先简单了解一下CPUCache的结构
CPU Cache是由很多个Cache Line组成的,CPU Line是CPU从内存读取数据的基本单位,而CPU Line是由各种标志(Tag)+数据块(Data Block) 组成
当我们读数据时,首先在Cache中检测数据,如果有的话,则直接从Cache中读,如果没有的话,那么先从主存中读出来,再存储在Cache中,但是,当我们在写数据时,我们将数据在Cache中修改后,那么Cache就和主存的数据不一致了,那么我们应该在什么时候把数据写回到主存呐?
写直达
写直达:在写数据时,把数据同时写入主存和Cache中
如果数据已经在Cache中,先将数据更新到Cache里面,再写入到主存里面
如果数据没有在Cache中,就直接把数据更新到主存中
无论数据在没在Cache中,最后都需要把数据写回到主存,因为IO操作特别慢,所以这会极大的影响效率
写回
为了减少IO操作,所以就出现了写回的方法
在写回机制中,当发生写操作时,新的数据仅仅被写入Cache 中,只有修改过的的Cache 块需要被替换时,才写回到主存中。减少了IO操作,这样可以大大提高程序的效率。
如果当发生写操作的时候,数据已经在CPU cache中,则把数据更新到CPU cache中,同时标记CPU
cache里的 cache block为脏(Dirty)的,(代表这个时候,CPU cache里面的这个cache block的数据和
内存是不一样的)
- 如果当发生写操作,数据所对应的cache block里存放的是其它的地址的数据A,这个时候就要检测这个cacheblock里的A有没有标记为脏。 如果标记为脏,我们就要把A写回到内存,同时把要写入的数据写入到cache block中,并标记为脏。
- 如果没有的话,就直接将要写入的数据写入到cache block中,再把这个数据标记为脏。
通过写回的话,如果缓存命中的话,可以大大提高我们的性能。
缓存一致性
现在CPU都是多核的,由于L1/L2 cache都是每个核独自的,这样就会带来多核的CPU缓存一致性的问题。
举例:
假如core1,core2各自运行一个线程,它俩同时对一个变量i(初始值)进行操作,假如说core1对i进行++操作,按照写回策略,先把i的值写回到L1/L2 cache中,再把它标记为脏,这个时候core2对i操作的时候,直接从内存中读值,这个时候,读到的值就是错误的,因为刚才对i的操作还没有同步到内存中,这就是缓存一致性问题。
要实现CPU缓存一致性的话,我们就要保证:
1.某个CPU核心李的cache 数据更新时,必须传播到其它核心中的cache里,这就是写传播。
2.某个CPU核心里对数据的操作顺序,必须在其它核心看起来顺序是一样的,这就是事务的串行化。
我们简单解释一下事务的串行化,
假如说有一个4核的CPU,这4个core都操作同一个变量(初始值为0),core1先把i变为100,而同一时间core2再把值变为200,根据写传播,这两个操作都会传播到core3,core4。
- 假如说core3先接收了core1的操作,再收到了core2的操作,那么对与core3来说,i的值此时i的值就是200。
- 假如说core4相反,先接收了core2的操作,再收到了core1的操作,那么对于core4来说,此时i的值就是100。
所以我们要保证所有的核心看到的都是相同顺序的变化,这样的过程就是事务的串行化。
总线嗅探
写传播最常见的方式就是总线嗅探。
实现原理:每个CPU都监听总线上的广播事件,并检查是否有相同的数据在自己的L1/L2缓存中,如果有的话,则自己也需要把该数据更新。
MESI协议
MESI协议基于总线嗅探机制实现了事务串行化,也用状态机机制降低了总线带宽压力。
MESI协议其实就是4个状态单词的开头字母缩写:
- Modified 已修改
- Exclusive 独占
- Shared 共享
- Invalidated 已失效
用这四个状态来标记cache Line四个不同的状态。
已修改状态:就是我们前面提到的脏标记,代表该cache block上的数据已经被更新过,但是还没有写到内存里。
已失效状态: 表示的是这个cache block里的数据已经失效了,不可以读取该状态的数据。
独占和共享都代表cache block里的数据是干净的(cache block和内存里面的数据是一致的)。
独占和共享的差别在于,独占状态的时候,数据只存储在一个CPU的cache中,而其它CPU cache里面没有该数据。这个时候,如果要向独占的cache写数据,就可以自由的写入,而不需要通知其它CPU core。而如果有其它core从内存读取了相同的数据到自己的cache中,那么独占的数据就会变为共享状态。
共享状态:代表着相同的数据在多个CPU核心的cache里面都有,所以当我们要修改cache里面的数据时,不能直接修改,而是要先向所有其它的core广播一个请求,要求先把其它core中cache中对应的cache line标记为无效状态,然后再更新当前cache里面的数据。