大纲:
1.CPU Cache的产生背景
2.CPU Cache模型
3.Cache Line
4.Cache Bouncing
5.CPU、CPU Cache、Main Memory交互
6.CPU 缓存一致性
7.Java内存模型
1.CPU Cache的产生背景
计算机中的所有运算操作都是由CPU的寄存器来完成的,CPU指令的执行过程需要涉及数据的读取和写入,这些数据只能来自于计算机主存(通常指RAM)。
CPU的处理速度和内存的访问速度差距巨大,直连内存的访问方式使得CPU资源没有得到充分合理的利用,于是产生了在CPU与主存之间增加高速缓存CPU Cache的设计。
2.CPU Cache模型
以下为CPU Cache模型,缓存分为三级L1/L2/L3,由于指令和数据的行为和热点分布差异很大,因此将L1按照用途划分为L1i(instruction)和L1d(data).
在多核CPU的结构中,L1和L2是CPU私有的,L3则是所有CPU共享的。
3.Cache Line
CPU Cache是由多个Cache Line构成的,Cache Line是CPU Cache与内存数据交换的最小单位,可以认为是CPU Cache中最小的缓存单元,目前主流CPU Cache大小都是64字节。
状态:在MESI协议中,状态可以是M、E、S、I。
地址:Cache Line中映射的内存地址。
数据:从内存中读取的数据。
4.Cache Bouncing
L3是多核共享的,为了保证所有的核看到正确的内存数据,一个核在写入自己的L1 cache后,CPU会执行Cache一致性算法把对应的Cache Line同步到其他核。这个过程并不很快,是微秒级的,相比之下写入L1 cache只需要若干纳秒。当很多线程在频繁修改某个字段时,这个字段所在的Cache Line被不停地同步到不同的核上,就像在核间弹来弹去,这个现象就叫做Cache Bouncing。由于实现CPU Cache一致性往往有硬件锁,Cache Bouncing是一种隐式的的全局竞争。
Cache Bouncing使访问频繁修改的变量的开销陡增,甚至还会使访问同一个Cache Line中不常修改的变量也变慢,这个现象是falsesharing。按Cache Line对齐能避免false sharing,但在某些情况下,我们甚至还能避免修改“必须”修改的变量。当很多线程都在累加一个计数器时,我们让每个线程累加私有的变量而不参与全局竞争,在读取时我们累加所有线程的私有变量。虽然读比之前慢多了,但由于这类计数器的读多为低频展现,慢点无所谓。而写就快多了,从微秒到纳秒,几百倍的差距。
5.CPU、CPU Cache、Main Memory交互
CPU Cache的出现是为了解决CPU直接访问内存效率低下的问题,程序在运行过程中,先将运算所需要的数据从主存复制一份导CPU Cache中,CPU运算时直接对CPU Cache中的数据进行读取和写入,当运算结束之后,再将CPU Cache中的最新数据刷新到主存中。
6.CPU 缓存一致性
CPU Cache的出现极大地提高了CPU的吞吐能力,但是同时引入了缓存不一致问题,比如i++操作的处理过程:
- 读取主内存的i到CPU Cache;
- 从CPU Cache中读取i;
- 在CPU寄存器中对i进行加1操作;
- 将结果i写回到CPU Cache;
- 将数据i刷新到主存;
在多线程情况下,每个线程都有自己的工作内存(本地内存),共享变量i会在多个线程的本地内存中存储一个副本。如果两个线程同时执行i++操作,假设i的初始值是1,每一个线程都从主内存中获取i的值存入CPU Cache中,执行加1操作再写入到主存中,都自增1可能就会导致两次自增之后的结果是2,这就是典型的CPU 缓存不一致性问题。主流解决方案有如下两种:
总线加锁:常见于早期CPU,CPU和其他组件的通信都是通过总线来进行的,总线加锁会阻塞其他CPU的操作,只有抢到总线锁的CPU能够操作。
缓存一致性协议:最出名的是Intel的MESI协议,它保证了每一个缓存中使用的共享变量副本是一致的。当CPU操作CPU Cache中的一个共享变量时,此时在其他CPU Cache中也可能存在一个副本,如果是写入操作,则发出信号通知其他CPU这个变量的Cache Line的装填置为无效状态,其他CPU在进行该变量的读取操作时需要从主存中再次获取。
MESI协议将Cache Line的状态分成以下四种:
M-modify(修改):当前CPU Cache拥有最新数据(最新的Cache Line),其他CPU拥有失效数据(Cache Line的状态是invalid),虽然当前CPU中的数据和主存是不一致的,但是以当前CPU的数据为准;
E-exclusive(独占):只有当前CPU Cache中有数据,其他CPU Cache中没有该数据,当前CPU的数据和主存中的数据是一致的;
S-shared(共享):当前CPU Cache和其他CPU Cache中都有共同数据,并且和主存中的数据一致;
I-invalid(失效):当前CPU Cache中的数据失效,数据应该从主存中获取,其他CPU Cache中可能有数据也可能无数据,当前CPU Cache中的数据和主存被认为是不一致的,对于invalid而言,在MESI协议中采取的是写失效(Write invalidate)。
缓存一致性协议解决数据不一致问题
7.Java内存模型
JMM:Java Memory Mode,java内存模型,指定了Java虚拟机如何与计算机的主存进行工作。
Java内存模型决定了一个线程对共享变量的写入何时对其他线程可见,Java内存模型定义了一个线程和主内存之间的抽象关系,具体如下:
- 共享变量存储于主存,每个线程都可以访问;
- 每个线程都有私有的工作内存或称为本地内存,每一个线程都不能访问其他线程的工作内存或本地内存;
- 工作内存只存储该线程对共享变量的副本;
- 线程对变量的所有操作都必须在自己的工作内存中进行,线程不能直接操作主内存,只有先操作了工作内存之后才能写入主存;
- 工作内存和Java内存模型一样是一个抽象概念,它其实并不存在,它涵盖了缓存、寄存器、编译优化以及硬件等。
Java内存模型(JMM)