一、CPU缓存结构
现代CPU通常都是由三层缓存架构组成的,如下图所示:
CPU缓存结构.pngwindows下的cpu:
windows查看linux的cpu缓存如下:
[root@public-server9 ~]# lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 1
Core(s) per socket: 4
座: 1
NUMA 节点: 1
厂商 ID: GenuineIntel
CPU 系列: 6
型号: 79
型号名称: Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz
步进: 1
CPU MHz: 2399.998
BogoMIPS: 4799.99
超管理器厂商: VMware
虚拟化类型: 完全
L1d 缓存: 32K
L1i 缓存: 32K
L2 缓存: 256K
L3 缓存: 35840K
-
各缓存之间的效率如下所示:
假设执行一条指令
名称 大约需要的时钟周期(cycle) 寄存器 1 L1 3~4 L2 10~20 L3 40~45 内存 120~240 -
为什么如此设计?
缓存的意义在于缓存热点数据,随着科技进步以及热点数据的逐步增加,一级缓存已经不能满足了。
二级缓存是一级缓存的缓冲,一级缓存速度快,造价高,容量小。
三级缓存则作为二级缓存的缓冲,速度相对于二级缓存还要更慢。不同之处在于,三级缓存时多个核心共享的一个缓冲。可以认为 是一个更小但是更快的内存。
二、缓存行(Cache LIne)
相信大家应该都听过缓存行,作为CPU缓存中的最小缓存单元,通常是大小是64字节。
我们可以使用如下的方式查看linux下的缓存行大小:
[root@public-server9 ~]# cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
64
CPU当中数据的移动不是以一个字节为单位,而是以一个缓存行为单位的。
当 CPU 把内存的数据载入缓存时,会把临近的64Byte的数据一同放入同一个Cache line中。
根据空间局部性原理:临近的数据在将来被访问的可能性大。
三、CPU缓存一致性
多个cpu对同一块内存同时读写,会引起冲突问题,这一问题就是CPU的缓存一致性问题。
如何保证多级缓存中的数据一致性呢?
MESI协议 是基于Invalidate的高速缓存一致性协议,并且是支持回写高速缓存的最常用协议之一。MESI协议要求在缓存不命中且数据块在另一个缓存时,允许缓存到缓存的数据复制。这样一来减少了主存的事务操作,极大提高了性能。
MESI中每个缓存行都有四个状态,分别是:
-
E(exclusive)独占
缓存行只在当前缓存中,但是是干净的(clean)-- 缓存数据与主存数据相同。
当别的缓存读取它时,状态变为共享(S);
当前写数据时,变为已修改状态(M)。 -
M(modified)已修改
缓存行是脏的(dirty),与主存的值不同。如果别的CPU内核要读主存这块数据,该缓存行必须回写到主存,状态变为共享(S)。
-
S(shared)共享
缓存行也存在于其它缓存中且是干净的。
-
I(invalid)无效
缓存行是无效的
下面简要描述一下状态转换的流程和关系:
1)M、E、S状态下的缓存行,都可以满足CPU的读请求;I状态是无效的,会重新去获取。
2)E状态下的缓存行,当发生写请求时,会将状态转换成M,但此时并不向主存同步。E状态下的缓存行要监听读请求,当有读请求时,需要将状态变为S。
3)M状态下的缓存行,需要监听其读操作,如果发生读操作,会将其他缓存当中的该缓存行(S状态),变成I状态,并且将自己写入主存,然后变成S状态。
4)S状态的缓存行,如果发生写请求,会将自己变成M状态,如果有读请求,则会重复3)中的步骤,将其他缓存中的缓存行变成I状态,将自己写入主存,变成S状态。
5)S状态下的缓存行,需要监听该缓存行的失效操作,如果发生失效操作,需要将自身变成I状态。
6)I状态的缓存行发生读请求,需要从主存获取。
上面就形成了一个闭环。
本文主要对CPU的缓存一致性做一个入门了解,便于多线程并发编程的学习,过多的本文将不讲解了。
四、内存屏障
下面简单了解一下什么是内存屏障。
前面的文章当中,我们曾提到过线程间的可见性,以及有序性,那么是如何实现的呢?当时我们说是使用volatile关键字,其底层的本质就是通过内存屏障。
内存屏障主要分为读屏障和写屏障。
-
可见性
- 写屏障:对于共享变量的修改,在写屏障之前,都要同步到主存当中。
- 读屏障:对于共享变量的修改,在读屏障之后,都要加载主存中的数据。
-
有序性
- 写屏障:确保发生指令重排序时,不会将写屏障前的数据排在写屏障的后面。
- 读屏障:确保发生指令重排序时,不会将读屏障之后的代码排在读屏障之前。
本篇简单了解原理,下一章节我们会学习volatile的原理。
网友评论