CPU的缓存结构
我们知道CPU的运算速度是很快的,因为从磁盘获取的数据的速度严重影响着效率,所以此有了DRAM(内存),但是即便是这样,内存的性能也远远跟不上CPU的运行速度,所以CPU的设计者开始在内部加入SRAM (高速缓存) ,来解决CPU运算速度和内存IO的不匹配问题。
作为高速缓存,他的大小和成本都使得缓存的空间有限。我们将要用到的数据放在更加接近CPU的地方,这样每次取的时候就不需要从内存中去拿。一般CPU为我们提供了三级缓存,一般称为L1,L2,L3。其中L1层分为2块:L1P-用于存放程序(program),L1D-用于存放数据(data)。L1和L2是单核独占的(也有部分说法说L2会被相邻的2个core共享,没仔细考究),而L3是单个CPU里面的多核共享的(毕竟土豪的电脑可能有2块cpu)。CPU在获取数据的时候,会先从L1找,找不到再去L2,L3,DRAM这个顺序依次找到为止。
缓存一致性问题
因为CPU的多核存在,然后每个核都有自己的独立的工作空间,所以当两个核都需要处理同一个内存中的数据的时候,他们都会将数据从内存中拷贝到自己的缓存中,我们这里先把他这块叫工作内存,这样如果2个核心对应的线程都需要修改这个值的时候,那么就会出问题,当处理完成后,我们还是要将数据写回到缓存中去的,但是因为这2个互相独立的核心,他们都写回的话,肯定会有一个被另外一个给覆盖了(还有相关的一系列的并发读写引发的问题)。
解决方法
下面列举了一些可以来处理上述缓存一致性问题的一些方案:
共用一个缓存
这种方式就类似于将一个多线程问题,通过同步的方式解决一样,公用一个缓存,那么在某个核心在使用该数据的时候,其他的核心将会被阻塞等待,这样也会降低CPU的效率。
在总线(bus)上加lock锁的方式
这个是在不修改对应硬件结构上,来让DRAM的数据只能由1个核心加载到自己缓存中。我们从前面CPU的工作原理,大概可以得知CPU从RAM获取数据基本都是通过将CPU和RAM通过总线进行连通来通信的。这种方式和上面一样不好,锁住总线会让其他和其不想干的CPU都无法访问RAM,相当于一个悲观锁,效率比较低下。
缓存一致性协议的方式 MESI
通过锁定缓存行的方式来来保证多核CPU和内存读写的一致性问题。其方案类似于读写锁的方式,使得针对同一地址的读内存操作是并发的,而针对同一地址的写内存操作是独占的。
MESI
MESI是Modified、Exclusive、Shared、Invalid这四个单词的首字母。这4个字母分别代表4种状态:
状态 | 描述 | 监听任务 |
---|---|---|
Modified(修改) | 该缓存行有效,但是该缓存数据已经被当前核心修改,此时和DRAM中数据不一致。我们将其置为M,其他的核中缓存行都会置为I。 | 监听总线上所有对该缓存行写回DRAM的操作(不希望别人写入),需要将该操作延迟到自己将缓存行写回到主存后变成S状态。 |
Exclusive(互斥) | 该缓存行有效,数据和RAM的数据一致,数据只存在当前内核工作内存中,只有他在使用是独占的。 | 监听总线上所有从DRAM读取该缓存行的操作,一旦有读的,需要将状态置为S状态。 |
Shared(共享) | 该缓存行有效,不过当前缓存行在多个核中都有,并且大家以及DRAM中的都一样。 | 监听其他的缓存中将该缓存置为I或者为E的事件,将状态置为I状态。 |
Invalid(无效) | 表明该缓存行无效,如果想要获取数据的话,就去DRAM中加载最新的。 | 不需要监听。 |
先暂时不用去理解描述后面写的内容,我们先得了解2个概念:
缓存行
在CPU缓存中最小的存储单元称为缓存行(cache line),一般大小为64B。我们需要在缓存行中保存上面的4种状态,所以一个缓存行中,都只要有2个Bit位去存储该状态(下图flag标志位,tag用来定位缓存行位置)。
监听
并且上面我们可以得知当有一个核去修改了自己的缓存行,需要同步到其他的核并更新他们的状态。所以说在MESI中每个cache控制器,不仅需要知道自己的操作,还会监听其他的cache的操作。
我们把CPU各个内核对缓存的操作可以总结为4种操作:
- local read:CPU内核读取自己的本地缓存
- local write:CPU内核写入自己的本地缓存
- remote read:其他的CPU内核读取了DRAM中当前内核的缓存行
- remote write:其他的CPU内核写入了DRAM中当前内核的缓存行
CPU内核中的缓存会监听这些事件来修改自己缓存的缓存行中的Flag标志位。然后通过该标志位来决定CPU如何处理这个缓存数据。
MESI状态转移图
上面的MESI四种状态,在监听到那四种操作之后会依据操作的类型来转换状态:
我们这里用CPU的两个核CA(coreA)和CB(coreB)以及缓存行数据 X 来解释下上面图片的转变,当CA中存在X:
状态是M(修改):此时只有CA内部有X,并且X和RAM的X值是不一致的。
事件 | 行为 | 下一个状态 |
---|---|---|
local read | 直接从CA的cache中读,状态不发生改变 | M |
local write | 直接修改CA的cache数据,状态不变 | M |
remote read | CB需要最新数据,将CA的X值写回到RAM中 CB从RAM再读取X,CA和CB的缓存行标志为设置为S |
S |
remote write | 先将CA的X值写回到RAM中 CB读取X并修改,CA的状态变为I,CB的状态变为M |
I |
状态是S(共享):此时CA和CB都有X,且和RAM的值都一致。
事件 | 行为 | 下一个状态 |
---|---|---|
local read | 直接从CA的cache中读,状态不发生改变 | S |
local write | CA直接修改cache,状态变为M。CB变为I | M |
remote read | CB读的和CA的一样的数据,状态不变 | S |
remote write | CB对应成了上面CA的local write,CB的cache变为M,CA的变为I | I |
状态是E(独占):此时只有CA有X,且X和RAM的X值一致(值一致代表着不需要写回RAM)
事件 | 行为 | 下一个状态 |
---|---|---|
local read | 直接从CA的cache中读,状态不发生改变 | E |
local write | 直接修改CA的cache,状态变为M | M |
remote read | CB发送读事件,CA和CB需要共享X,所以状态都变为S | S |
remote write | CA将X置为I | I |
状态是I(失效):需要依据CB是否有X,以及响应的状态来决定操作。
事件 | 行为 | 下一个状态 |
---|---|---|
local read | 如果CB没有X,则CA读取X,状态为E 如果CB有X,状态为M,则CB需要写回到RAM,然后CA读取,状态变为S 如果CB有X,状态为S或者E,那么CA直接读,CA和CB都变为S |
E or S |
local write | CA需要从RAM拉取数据 如果CB没有X,那么CA就直接拉取,修改后设置为M 如果CB有X,状态为M,那么CB需要先写回RAM,然后CA读取最新到cache,修改并设置为M,CB变为I 如果CB有X,状态为S或者E,那么CA读取并设置为M,CB为I |
M |
remote read | 已经失效,只和自己读写有关,和其他的读写无关,状态不变。 | I |
remote write | 已经失效,只和自己读写有关,和其他的读写无关,状态不变。 | I |
上面写的可能复杂,但是其实原理很简单!只要了解了原理,你可以自己对着这些状态就能演算出来,主要抓住几个规则:
- 当有核心读的时候,需要关注其他核心的状态是否有M的,有M的必须要先让M的写入到RAM,再读最新数据。即:当read操作需要到RAM中取时,整个CPU层面不能有该缓存行状态为M的,有的必须让其先写回到RAM中。
- 当有核心写的时候,涉及会比较多,但是核心就是:写的时候CPU层面不能有任何其他核心处于M状态,有就需要将其先写回RAM,拿到最新的数据修改,修改完成,该核心以外的核都变为失效。
- 所有的这些操作都是为保证:任何核心的修改不能被覆盖!任何核心的读取,都需要拿到当前CPU缓存层面最新的值!
- 上面的状态很多都是相互的对应案例,比如CA的remote read,对应这CB的local read。
大致再简述下这个流程:
CA和CB以缓存行X,CB可以代表若干其他的CPU内核。
先看CA单核情况下:
- CA第一次读X,此时X状态为E
- CA修改了X的值,此时X状态为M
- CA将X值如果再写回到RAM中,那么X状态又变为E
这时候加入CB参与进来:
- CA第一次读X,此时X状态为E
- CB也读了X,此时X就应该是共享的,所以CA和CB都是S
- CB想写入X,那么CB需要先将X读进来,则先2个都变为S,然后B修改了X,状态变为M,并将该修改消息发送到总线,CA监听后将再本地X置为I,因为CB有最新的数据了,但是又没有写到RAM,我们既拿不到,也不能用自己cache里面的了。(个人理解,没有考究)
- CA修改了X的值,此时X状态为M
- CB想要读X,CPU中不能存在修改了X但还没有提交到RAM的数据,所以CA需要先将X写回到RAM,状态就会变为E,才是CB再读,CA监听读事件,CA和CB再变为S。
- CB想要写X,因为CA是先于CB将X修改的,所以需要先将CA的写回到RAM,然后CB重新读取X后,然后再修改X,然后CA的就变成无效的I了,CB获的独占权变为M。
(过程只是便于理解状态的转换,具体CPU怎么操作并非真实如此)
参考
近期评论