CPU 在摩尔定律的指导下以每 18 个月翻一番的速度在发展,然而内存和硬盘的发展速度远远不及 CPU。这就形成了高性能能的内存和硬盘价格及其昂贵。然而 CPU 的高度运算须要高速的数据。为了解决这个问题,CPU 厂商在 CPU 中内置了少许的高速缓存以解决 I\O 速度和 CPU 运算速度之间的不匹配问题。html
在 CPU 访问存储设备时,不管是存取数据抑或存取指令,都趋于汇集在一片连续的区域中,这就被称为局部性原理。数组
时间局部性(Temporal Locality):若是一个信息项正在被访问,那么在近期它极可能还会被再次访问。
好比循环、递归、方法的反复调用等。缓存
空间局部性(Spatial Locality):若是一个存储器的位置被引用,那么未来他附近的位置也会被引用。
好比顺序执行的代码、连续建立的两个对象、数组等。性能
因为 CPU 的运算速度超越了 1 级缓存的数据 I\O 能力,CPU 厂商又引入了多级的缓存结构。优化
多级缓存结构spa
多核 CPU 的状况下有多个一级缓存,如何保证缓存内部数据的一致, 不让系统数据混乱。这里就引出了一个一致性的协议 MESI。设计
MESI 是指 4 中状态的首字母。每一个 Cache line 有 4 个状态,可用 2 个 bit 表示,它们分别是:code
缓存行(Cache line): 缓存存储数据的单元。
状态 | 描述 | 监放任务 |
---|---|---|
M 修改 (Modified) | 该 Cache line 有效,数据被修改了,和内存中的数据不一致,数据只存在于本 Cache 中。 | 缓存行必须时刻监听全部试图读该缓存行相对就主存的操做,这种操做必须在缓存将该缓存行写回主存并将状态变成 S(共享)状态以前被延迟执行。 |
E 独享、互斥 (Exclusive) | 该 Cache line 有效,数据和内存中的数据一致,数据只存在于本 Cache 中。 | 缓存行也必须监听其它缓存读主存中该缓存行的操做,一旦有这种操做,该缓存行须要变成 S(共享)状态。 |
S 共享 (Shared) | 该 Cache line 有效,数据和内存中的数据一致,数据存在于不少 Cache 中。 | 缓存行也必须监听其它缓存使该缓存行无效或者独享该缓存行的请求,并将该缓存行变成无效(Invalid)。 |
I 无效 (Invalid) | 该 Cache line 无效。 | 无 |
触发事件 | 描述 |
---|---|
本地读取(Local read) | 本地 cache 读取本地 cache 数据 |
本地写入(Local write) | 本地 cache 写入本地 cache 数据 |
远端读取(Remote read) | 其余 cache 读取本地 cache 数据 |
远端写入(Remote write) | 其余 cache 写入本地 cache 数据 |
状态 | 触发本地读取 | 触发本地写入 | 触发远端读取 | 触发远端写入 |
---|---|---|---|---|
M 状态(修改) | 本地 cache:M 触发 cache:M 其余 cache:I |
本地 cache:M 触发 cache:M 其余 cache:I |
本地 cache:M→E→S 触发 cache:I→S 其余 cache:I→S 同步主内存后修改成 E 独享, 同步触发、其余 cache 后本地、触发、其余 cache 修改成 S 共享 |
本地 cache:M→E→S→I 触发 cache:I→S→E→M 其余 cache:I→S→I 同步和读取同样, 同步完成后触发 cache 改成 M,本地、其余 cache 改成 I |
E 状态(独享) | 本地 cache:E 触发 cache:E 其余 cache:I |
本地 cache:E→M 触发 cache:E→M 其余 cache:I 本地 cache 变动为 M, 其余 cache 状态应当是 I(无效) |
本地 cache:E→S 触发 cache:I→S 其余 cache:I→S 当其余 cache 要读取该数据时,其余、触发、本地 cache 都被设置为 S(共享) |
本地 cache:E→S→I 触发 cache:I→S→E→M 其余 cache:I→S→I 当触发 cache 修改本地 cache 独享数据时时,将本地、触发、其余 cache 修改成 S 共享. 而后触发 cache 修改成独享,其余、本地 cache 修改成 I(无效),触发 cache 再修改成 M |
S 状态 (共享) | 本地 cache:S 触发 cache:S 其余 cache:S |
本地 cache:S→E→M 触发 cache:S→E→M 其余 cache:S→I 当本地 cache 修改时,将本地 cache 修改成 E, 其余 cache 修改成 I, 而后再将本地 cache 为 M 状态 |
本地 cache:S 触发 cache:S 其余 cache:S |
本地 cache:S→I 触发 cache:S→E→M 其余 cache:S→I 当触发 cache 要修改本地共享数据时,触发 cache 修改成 E(独享), 本地、其余 cache 修改成 I(无效), 触发 cache 再次修改成 M(修改) |
I 状态(无效) | 本地 cache:I→S 或者 I→E 触发 cache:I→S 或者 I →E 其余 cache:E、M、I→S、I 本地、触发 cache 将从 I 无效修改成 S 共享或者 E 独享,其余 cache 将从 E、M、I 变为 S 或者 I |
本地 cache:I→S→E→M 触发 cache:I→S→E→M 其余 cache:M、E、S→S→I |
既然是本 cache 是 I,其余 cache 操做与它无关 | 既然是本 cache 是 I,其余 cache 操做与它无关 |
M | E | S | I | |
---|---|---|---|---|
M | × | × | × | √ |
E | × | × | × | √ |
S | × | × | √ | √ |
I | √ | √ | √ | √ |
`
value = 3; void exeToCPUA(){ value = 10; isFinsh = true; } void exeToCPUB(){ if(isFinsh){ //value必定等于10?! assert value == 10; } }
`
试想一下开始执行时,CPU A 保存着 finished 在 E(独享) 状态,而 value 并无保存在它的缓存中。(例如,Invalid)。在这种状况下,value 会比 finished 更迟地抛弃存储缓存。彻底有可能 CPU B 读取 finished 的值为 true,而 value 的值不等于 10。
即 isFinsh 的赋值在 value 赋值以前。 这种在可识别的行为中发生的变化称为重排序(reordings)。注意,这不意味着你的指令的位置被恶意(或者好意)地更改。 它只是意味着其余的 CPU 会读到跟程序中写入的顺序不同的结果。 ~顺便提一下 NIO 的设计和 Store Bufferes 的设计是很是相像的。~ ### 硬件内存模型 执行失效也不是一个简单的操做,它须要处理器去处理。另外,存储缓存(Store Buffers)并非无穷大的,因此处理器有时须要等待失效确认的返回。这两个操做都会使得性能大幅下降。为了应付这种状况,引入了失效队列。它们的约定以下: * 对于全部的收到的 Invalidate 请求,Invalidate Acknowlege 消息必须马上发送 * Invalidate 并不真正执行,而是被放在一个特殊的队列中,在方便的时候才会去执行。 * 处理器不会发送任何消息给所处理的缓存条目,直到它处理 Invalidate。 即使是这样处理器已然不知道何时优化是容许的,而何时并不容许。 干脆处理器将这个任务丢给了写代码的人。这就是内存屏障(Memory Barriers)。 >
写屏障 Store Memory Barrier(a.k.a. ST, SMB, smp_wmb) 是一条告诉处理器在执行这以后的指令以前,应用全部已经在存储缓存(store buffer)中的保存的指令。 >
读屏障 Load Memory Barrier (a.k.a. LD, RMB, smp_rmb) 是一条告诉处理器在执行任何的加载前,先应用全部已经在失效队列中的失效操做的指令。
`
void executedOnCpu0() { value = 10; //在更新数据以前必须将全部存储缓存(store buffer)中的指令执行完毕。 storeMemoryBarrier(); finished = true; } void executedOnCpu1() { while(!finished); //在读取以前将全部失效队列中关于该数据的指令执行完毕。 loadMemoryBarrier(); assert value == 10; }
`
##### 引用文章
http://www.importnew.com/1058...
https://www.cnblogs.com/yanlo...