参考资料
https://time.geekbang.org/column/article/13484?code=XamkmJYooKBPKe8hT7otClsFDeVHb6rptMYHTdgiRPU%3D
https://www.jianshu.com/p/43af2cc32f90
https://www.jianshu.com/p/7d3150fc0277
https://juejin.im/post/5bf2977751882505d840321d
https://www.jianshu.com/p/506c1e38a922
https://blog.csdn.net/hemmingway/article/details/38750913
高速缓存的结构
高速缓存有哪些
L1i Cache
- 1.存放需要执行的数据的缓冲
- 2.代表该数据正在被使用,比如放入到cpu的寄存器上进行操作
L1d Cache
- 1.存放执行数据需要的指令解码
- 2.2.代表该指令正在被使用,比如放入到cpu的寄存器上进行操作
L2 Cache
- 1.存放数据的缓存
- 2.分内部和外部两种芯片,内部芯片速度基本上与CPU主频相同,而外部芯片只有主频的一半。
- 3.代表数据即将被使用
L3 Cache
- 1.存放数据的缓存
- 2.离CPU较远,读取速度没一级二级快,但一般三级缓存容量比前面两级大很多。
- 3.类似于预读的特性,从主内存中获取一部分数据缓存,避免当1,2级缓存没有命中进而去主内存中load。
三级缓存的意义:虽然容量比主内存小,但是交互速度快进而提高cpu的效率。对于大的数据会直接从主内存获取,绕过cache。
每个cpu有自己的上述三级缓存,如果一个cpu有多核,则每个核心有自己的L1(有可能还有L2),共享L3(有可能有L2)
三级缓存的内部结构
无论是L1,L2,L3他们的内部结构都相似(相同)
- 1.首先一个cache类似于一个Map,它分为若干桶,每个桶是一个链表,包含若干缓存条目
- 2.缓存条目=tag+data Block+flag。
- 3.Data Block:缓存行,存储从主内存中读取的数据以及准备写入主内存的数据,一个缓存行可能包含多个变量。
- 4.tag:缓存行中数据的位置信息,不同的core中tag应该是一直的(类似于一份数据在不同缓存系统中都是一样的)
- 5.Flag 代表该缓存行是否失效。
cpu访问内存时候的步骤如下
- 1.通过内存地址解码的三个数据index(类似于hash值可以定位在cache的哪个位置),tag(缓存条目的编号),offset(变量在缓存条目的位置偏移)来获取三级缓存中的数据
- 2.如果找到且该缓存行的Flag为有效,则缓存命中;
- 3.如果未命中则会去找下一级缓存,如果三级缓存都未命中,则会去主内存中获取对应的数据,这个过程会导致cpu core处于停顿状态 无法执行其他指令。
缓存一致性协议(MESI)
因为每个cpu core都有自己的高速缓存,为了避免出现其中的core对缓存进行更新后其他的core可以感知,所以设置了MESI协议。
M:被修改modified
- 1.该缓存行的数据是被修改的,与主内存的不一致。
- 2.每个core中tag值相同的缓存条目,只有一个能处于该状态。
- 3.在其他cpu读取同一个tag的缓存条目,被修改的缓存行的数据会写回主内存,然后变成E(独享exclusive)
E:独享exclusive
- 1.该缓存行以独占的方式保留了相应内存地址的副本数据。
- 2.其他所有cpu core上的高速缓存都不能保留该数据的有效副本。(这就意味着其他cpu core的缓存都失效了)
- 3.一旦其他的cpu core去主内存获取数据的时候,该数据缓存行状态变为shared。(不同的core的相同数据的缓存行的状态都是shared)
- 4.当cpu core修改缓存行,则该cpu core中的缓存行状态变为Modified。
S:共享的(shared)
- 1.该缓存行的数据可能被多个cpu core缓存(它们在不同的core中的 缓存行的tag都是一样的)
- 2.各个缓存中的数据与主内存中的数据一致。
- 3.一旦某个cpu core 修改了该缓存行数据时,其他cpu中该缓存行可以被作废(Invalid)
I:无效的(Invalid)
- 1.该缓存行是无效的,不包含任何内存地址对应的有效副本数据。
- 2.该状态是缓存条目的初始状态。
总结:首先各个core中初始的缓存行都是Invalid,当其中一个A core去主内存中获取数据时候,其缓存行的状态变为Exclusive
,当B core也去获取同样的数据的到高速缓存时候A和B的缓存行状态都变为shared。当B修改自己缓存行的数据时候,B的缓存行状态变为Modified,A的缓存行状态变为Invalid.
只要有其他的CpuCore(如果是B core自己可以直接从自己高速缓存获取数据)去获取该缓存行中具体的数据,B core就会把修改的数据刷入到主内存。进而B core的缓存行状态变为Exclusive.
为了缓存之间的通讯,协调各个处理器的读、写内存操作,MESI协议还定义了下面的一组消息:
Read
- 1.当某个 cpu core准备读取主内存中的数据会通知其他的cpu core。
- 2.该消息中包含待读取数据的内存地址。
- 3.返回的消息我们称之为Read Response。
- 4.返回的是请求读取的数据,有可能是主内存返回的,也有可能是其他高速缓存返回的。
Invalidate
- 1.通知其他cpu core将高速缓存中指定内存地址对应的缓存条目状态为I,即通知这些处理器删除指定内存地址的副本数据。
- 2.返回的消息是Invalidate Acknowledge,其他cpu core会回复该消息,表示已经删除其高速缓存上面的数据。
Read Invalidate
- 1.由Read和Invalidate组合而成的复合消息。
- 2.作用在于通知其他处理器,当前处理器准备更新一个数据,请求其他处理器删除自己高速缓存中的副本数据
- 3.返回的消息就是Read Response和Invalidate Acknowledge。
- 4.read返回是指cpu core含有该副本才会返回,如果返回了 再给他们发送Invalidate进而可以删除副本数据。
WriteBack
- 1.该消息包含需要写入主内存的数据及其对应的内存地址。
当cpu在执行内存读写的时候,会在一定情况下往总线发送特定的请求消息(即上述4个消息),同时每个处理器还会嗅探(拦截)总线中由其他处理器发出的请求消息,并在一定条件下往总线回复相应的响应消息。
MESI协议对内存数据访问的控制类似读写锁,它使得针对同一地址的读内存操作是并发的,而针对同一地址的写内存操作是独占的。从而保障了缓存间数据的一致。
举例说明
并发读
- 1.当processor 0需要读取缓存中的数据S,如果S的状态是M,E,S那么可以直接使用。
- 2.如果是I,则Processor 0会通过总线发送read数据。这时候缓存状态不是I的Processor或者主内存收到消息会回复。
- 3.上述消息如果被Processor 1获取了 其他的处理器或者主内存无法获取,如果processor中存在M的则该processor会回复。
- 4.如果我们获取的Processor的缓存状态是M则其会先将数据写入主内存,此时状态为E,然后返回Read Response后,再将状态更新为S。
互斥写
- 1.当处理器Processor 0要向地址A(缓存条目的地址)中写数据时,如果地址A在Processor 0中的缓存条目状态为E,M,则代表其对该数据拥有独占权
可以直接写入到A,然后缓存状态变为M。 - 2.如果写的缓存条目状态为S,处理器需要往总线发送Invalidate消息来获取该缓存条目的独占权。
- 3.当接收到其他所有处理器返回的Invalidate Acknowledge消息后,处理器才会将数据更新到地址A中,并将对应的缓存条目改为M。
- 4.如果写的缓存条目状态为I,处理器Processor 0需要往总线发送Read Invalidate消息来获取该缓存条目的独占权,其他步骤同S
- 5.需要注意的是,如果接收到Invalidate消息的其他其他处理器,缓存条目状态为M,则该处理器会先将数据写入主内存(以方便发送Read Invalidate指令的处理器读到最新值),然后再将状态改为I。
MESI协议的缺陷:
依照上述的MESI协议虽然可以解决数据一致性的问题,但是存在性能缺陷:即处理器每次写数据都得等其他处理器将各自的高速缓存中的对应数据删除,并且接收到返回的Read Response与Invalidate Acknowledge消息后才执行写操作。
写缓冲器和无效化队列
为了解决上述的性能缺陷进而引入了写缓冲器和无效化队列
写缓冲器
- 1.写缓冲器是处理器内部一个容量比高速缓存还小的高速存储部件,每个处理器都有自身的写缓冲器,且一个处理器无法读取另一个处理器上的写缓冲器内容。
写操作过程
- 1.如果相应的缓存条目状态为 E、M,则直接写入,无需发送消息(照旧)
- 2.如果相应的缓存条目状态为 S, 处理器会将写操作相关信息存入写缓冲器,并发送Invalidate消息。(不再等待响应消息)
- 3.如果相应的缓存条目状态为 I,发生“写未命中”,将写操作相关信息存入写缓冲器,并发送Read Invalidate消息。(不再等待响应消息)
- 4.当处理器将写操作写入写缓冲器后,则认为写操作已经完成。而实际上,当处理器收到其他所有处理器回应的Read Response、Invalidate Acknowledge消息后,处理器才会将写缓冲器中对应的写操作写入相应的缓存行,这个时候,写操作才算真正完成。
总结:写缓冲器让处理器在执行写操作时不需要再额外的等待,减少了写操作的延时,提高了处理器的指令执行效率。
读操作过程
- 1.引入写缓存器后,处理器读取数据时,由于该数据的更新结果可能仍然停留在写缓冲器中,所以(自己的写缓冲只能从自己的处理器读取)处理器会先从写缓冲器中找寻数据,没有找到时,才从高速缓存中找。
- 2.这种处理器直接从写缓冲器中读取数据的技术被称为:存储转发
无效化队列
- 1.处理器在接收到Invalidate消息后,并不马上删除消息中指定地址对应的副本数据,而是将消息存入无效化队列之后就回复Invalidate Acknowledge消息,从而减少了执行写操作的处理器的等待时间。
- 2.需要注意的是,有些处理器(如X86)可能并没有使用无效化队列
写缓冲器和无效化队列带来的新问题
- 1.内存重排序与可见性
重排序
- 1.重排序有编译器的优化和运行期优化导致的重排序 也有因为写缓冲和无效队列导致的伪重排序(看起来效果像是重排序了)。
可见性
- 1.一个处理器的写缓冲器中的内容是无法被其他处理器读取的,这个也就造成了一个处理器更新一个共享变量后,对其他处理器而言,看不到这个更新的值,即可见性。
内存屏障
- 1.为了解决重排序和可见性
- 2.即保证执行下一条指令前对应得写缓冲和无效队列执行完毕才可以
- 3.内存屏障是volatile和synchronized得底层实现原理