前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >CPU缓存一致性协议MESI

CPU缓存一致性协议MESI

原创
作者头像
开源日记
修改2021-01-04 10:19:33
9980
修改2021-01-04 10:19:33
举报
文章被收录于专栏:JVMGCJVMGC

CPU 缓存一致性协议 MESI

================

CPU 高速缓存(Cache Memory)


CPU 为何要有高速缓存

CPU 在摩尔定律的指导下以每 18 个月翻一番的速度在发展,然而内存和硬盘的发展速度远远不及 CPU。这就造成了高性能能的内存和硬盘价格及其昂贵。然而 CPU 的高度运算需要高速的数据。为了解决这个问题,CPU 厂商在 CPU 中内置了少量的高速缓存以解决 I\O 速度和 CPU 运算速度之间的不匹配问题。

在 CPU 访问存储设备时,无论是存取数据抑或存取指令,都趋于聚集在一片连续的区域中,这就被称为局部性原理。

时间局部性(Temporal Locality):如果一个信息项正在被访问,那么在近期它很可能还会被再次访问。

比如循环、递归、方法的反复调用等。

空间局部性(Spatial Locality):如果一个存储器的位置被引用,那么将来他附近的位置也会被引用。

比如顺序执行的代码、连续创建的两个对象、数组等。

带有高速缓存的 CPU 执行计算的流程

  1. 程序以及数据被加载到主内存
  2. 指令和数据被加载到 CPU 的高速缓存
  3. CPU 执行指令,把结果写到高速缓存
  4. 高速缓存中的数据写回主内存

目前流行的多级缓存结构

由于 CPU 的运算速度超越了 1 级缓存的数据 I\O 能力,CPU 厂商又引入了多级的缓存结构。

多级缓存结构

多核 CPU 多级缓存一致性协议 MESI


多核 CPU 的情况下有多个一级缓存,如何保证缓存内部数据的一致, 不让系统数据混乱。这里就引出了一个一致性的协议 MESI。

MESI 协议缓存状态

MESI 是指 4 中状态的首字母。每个 Cache line 有 4 个状态,可用 2 个 bit 表示,它们分别是:

缓存行(Cache line): 缓存存储数据的单元。

触发事件

描述

本地读取(Local read)

本地 cache 读取本地 cache 数据

本地写入(Local write)

本地 cache 写入本地 cache 数据

远端读取(Remote read)

其他 cache 读取本地 cache 数据

远端写入(Remote write)

其他 cache 写入本地 cache 数据

2.cache 分类:

前提:所有的 cache 共同缓存了主内存中的某一条数据。

本地 cache: 指当前 cpu 的 cache。

触发 cache: 触发读写事件的 cache。

其他 cache: 指既除了以上两种之外的 cache。

注意:本地的事件触发 本地 cache 和触发 cache 为相同。

上图的切换解释:

M

E

S

I

M

×

×

×

E

×

×

×

S

×

×

I

举个栗子来说:

假设 cache 1 中有一个变量 x = 0 的 cache line 处于 S 状态 (共享)。

那么其他拥有 x 变量的 cache 2、cache 3 等 x 的 cache line 调整为 S 状态(共享)或者调整为 I 状态(无效)。

多核缓存协同操作

假设有三个 CPU A、B、C,对应三个缓存分别是 cache a、b、 c。在主内存中定义了 x 的引用值为 0。

单核读取

那么执行流程是:

CPU A 发出了一条指令,从主内存中读取 x。

从主内存通过 bus 读取到缓存中(远端读取 Remote read), 这是该 Cache line 修改为 E 状态(独享).

双核读取

那么执行流程是:

CPU A 发出了一条指令,从主内存中读取 x。

CPU A 从主内存通过 bus 读取到 cache a 中并将该 cache line 设置为 E 状态。

CPU B 发出了一条指令,从主内存中读取 x。

CPU B 试图从主内存中读取 x 时,CPU A 检测到了地址冲突。这时 CPU A 对相关数据做出响应。此时 x 存储于 cache a 和 cache b 中,x 在 chche a 和 cache b 中都被设置为 S 状态 (共享)。

修改数据

那么执行流程是:

CPU A 计算完成后发指令需要修改 x.

CPU A 将 x 设置为 M 状态(修改)并通知缓存了 x 的 CPU B, CPU B 将本地 cache b 中的 x 设置为 I 状态 (无效)

CPU A 对 x 进行赋值。

同步数据

那么执行流程是:

CPU B 发出了要读取 x 的指令。

CPU B 通知 CPU A,CPU A 将修改后的数据同步到主内存时 cache a 修改为 E(独享)

CPU A 同步 CPU B 的 x, 将 cache a 和同步后 cache b 中的 x 设置为 S 状态(共享)。

MESI 优化和他们引入的问题


缓存的一致性消息传递是要时间的,这就使其切换时会产生延迟。当一个缓存被切换状态时其他缓存收到消息完成各自的切换并且发出回应消息这么一长串的时间中 CPU 都会等待所有缓存响应完成。可能出现的阻塞都会导致各种各样的性能问题和稳定性问题。

CPU 切换状态阻塞解决 - 存储缓存(Store Bufferes)

比如你需要修改本地缓存中的一条信息,那么你必须将 I(无效)状态通知到其他拥有该缓存数据的 CPU 缓存中,并且等待确认。等待确认的过程会阻塞处理器,这会降低处理器的性能。应为这个等待远远比一个指令的执行时间长的多。

Store Bufferes

为了避免这种 CPU 运算能力的浪费,Store Bufferes 被引入使用。处理器把它想要写入到主存的值写到缓存,然后继续去处理其他事情。当所有失效确认(Invalidate Acknowledge)都接收到时,数据才会最终被提交。

这么做有两个风险

Store Bufferes 的风险

第一、就是处理器会尝试从存储缓存(Store buffer)中读取值,但它还没有进行提交。这个的解决方案称为 Store Forwarding,它使得加载的时候,如果存储缓存中存在,则进行返回。

第二、保存什么时候会完成,这个并没有任何保证。

代码语言:txt
复制
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) 是一条告诉处理器在执行任何的加载前,先应用所有已经在失效队列中的失效操作的指令。

代码语言:txt
复制
void executedOnCpu0() {
    value = 10;
    //在更新数据之前必须将所有存储缓存(store buffer)中的指令执行完毕。
    storeMemoryBarrier();
    finished = true;
}
void executedOnCpu1() {
    while(!finished);
    //在读取之前将所有失效队列中关于该数据的指令执行完毕。
    loadMemoryBarrier();
    assert value == 10;
}
引用文章

www.importnew.com/10589.html

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • CPU 为何要有高速缓存
  • 带有高速缓存的 CPU 执行计算的流程
  • 目前流行的多级缓存结构
  • MESI 协议缓存状态
  • 多核缓存协同操作
    • 单核读取
      • 双核读取
        • 修改数据
          • 同步数据
          • CPU 切换状态阻塞解决 - 存储缓存(Store Bufferes)
            • Store Bufferes
              • Store Bufferes 的风险
                • 引用文章
            • 硬件内存模型
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
            http://www.vxiaotou.com