1、缓存的作用
??CPU的结构很复杂,简单地说由运算器和寄存器组成。程序运行时,需要CPU去执行运算,运算是由运算器来执行,运算器可以做加减乘除运算以及与或非逻辑运算,运算过程中可能需要临时存放数据到某个地方,寄存器就起到这个作用。
??虽然寄存器可以存储一些运行时数据,但是容量是很小的,程序运行时产生的大部分数据(比如Java对象)是存储在内存中的,并且程序指令也是存储在内存中,所以程序运行时CPU需要频繁操作内存,包括读取和写入,但是CPU的速度太快了,如果直接操作内存,CPU的大部分时间会处于等待内存操作的空转状态,内存完全跟不上节奏,怎么办?
??这时候就需要有缓存的存在了,内存将CPU要读取的数据源源不断地加载到缓存中,CPU读取缓存,缓存的速度比内存快多了,勉强能跟得上CPU大哥的节奏了!
??但是CPU表示缓存你还是太慢了,我带不动,所以产生了一级缓存、二级缓存、三级缓存,一级缓存最快、二级次之、三级最慢;缓存容量则反过来,一级最小,二级大一些,三级最大。
??为什么缓存能加快系统运行?举个例子,现在需要很多水,如果直接打开水龙头,要放很久,如果有水桶已经放满了水,取水是不是会快点?如果需要更多的水,我们弄个水塔,平时储满水,假如水桶的水不够用,则打开水塔,这样就达到快速取水的目的。
??缓存可以看成是一个数据的池子,由于速度越快的缓存单位存储空间的价格也越高,所以要有多级缓存,速度快的存储小,速度慢的存储大,多级缓存结合达到总体上经济又实惠的效果,在三级缓存中,每一级缓存都有80%左右的命中率,如果本级缓存中找不到CPU要的数据,则进入下一级缓存中查找,三级缓存中找不到则进入内存查找,这种可能性只有0.8%,大多数情况下可以保证了CPU快速运行,避免内存延迟。
??CPU在读取数据时,先在L1中寻找,再从L2寻找,再从L3寻找,然后是内存,最后是外存储器。
2、缓存同步协议
??对于多核计算机,多个CPU可能会读取同样的数据进行缓存,在经过不同运算之后,最终写入主内存,那么问题来了,写入的时候谁先谁后,最终写入主内存中的数据以哪个CPU为准?
??为了应对这种高速缓存回写的场景,众多CPU厂商联合制定了缓存一致性协议MESI
协议,并分别实现,MESI
协议规定每条缓存有个状态位,同时定义了下面四个状态:
??当计算机中有多个处理器时,单个CPU对缓存中数据进行了改动,需要通知给其他CPU;这意味着CPU不仅要控制自己的读写操作,还要监听其他CPU发出的通知,从而保证最终一致。
3、高速缓存存在问题
??缓存中的数据与主内存的数据并不是实时同步的,各CPU(或CPU核心)间缓存的数据也不是实时同步的;在同一时间点,各CPU所看到同一内存地址的数据的值可能是不一致的。
??运行时指令重排是CPU为了避免阻塞等待某些操作需要的资源,先去执行可执行的指令,当阻塞等待的资源获取到时,再去执行对应的指令的操作
1、代码示例
??指令重排的场景:当CPU写缓存时发现缓存区块正在被其他CPU占用,为了提高CPU处理性能,可能将后面的读缓存命令优先执行。
【注意】对于单线程程序来说,需要遵循as-if-serial
语义,即不管指令如何被CPU重排,最终执行的效果都是一致的。
2、as-if-serial语义
??不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果不能被改变,编译器、runtime和处理器都必须遵循as-if-serial语义,也就是说,编译器和处理器不会对存在数据依赖关系的操作做重排序。
3、指令重排存在问题
??但是对多线程程序来说,指令逻辑无法分辨因果关联,因此指令重排可能会出现乱序执行,导致程序运行结果错误,因此在多线程程序中有些时候需要通类似volatile
修饰变量之类的方式来避免指令重排。
??为了解决高速缓存导致的缓存内存数据一致性问题,以及指令重排导致的程序乱序出错问题,处理器提供了两个内存屏障指令(Memory Barrier)。
1、写内存屏障(Store Memory Barrier)
??在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见;当发生这种强制写入主内存的显式调用,CPU就不会处于性能优化考虑进行指令重排。
2、读内存屏障(Load Memory Barrier)
??在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制重新从主内存加载数据,让CPU缓存与主内存保持一致,避免缓存导致的一致性问题。
原文:https://www.cnblogs.com/cnjf/p/14129357.html