前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Java并发编程】- 03 MESI、内存屏障

【Java并发编程】- 03 MESI、内存屏障

作者头像
Reactor2020
发布2023-03-22 19:03:30
7860
发布2023-03-22 19:03:30
举报

CPU Cache

我们知道计算机三大核心组件:CPU、内存和硬盘,其中CPU的处理速度是最快的,CPU的处理速度远远大于将数据从硬盘加载进来的速度,所以就导致CPU大部分都是空闲处于等待从硬盘加载数据这个流程上。然后就引入了内存,CPU从内存读取速度得到很大提升,然而依然存在很大瓶颈,为了提升CPU处理效率,生产厂商就在CPU上引入缓存Cache

CPU Cache常见的如上图采用三层缓存架构,L1、L2一般位于CPU核内部,而L3位于CPU核外部,一般用于多个CPU核之间数据共享。CPU Cache速度要远远大于内存的,所以,CPU Cache的出现主要是为了缓解CPU内存之间速度不匹配问题。

MESI协议

为了提升CPU性能CPU厂商引入CPU Cache概念,但是会带来一个问题:缓存一致性。L1、L2都位于CPU核内部,CPU可能存在多个核,它们之间缓存可能就会存在一致性问题。

CPU Cache会带来缓存一致性问题,那怎么去解决这个问题呢?有几种解决方案,其中比较通用的各种厂商通常都会支持的一种方案就是MESI协议,该协议就是用来解决CPU Cache之间缓存共享数据的一致性。

MESI是由四个单词首字母简写来的,这四个单词是用来描述cache lineCPU Cache中的四种不同状态:

  • 修改态(Modified):此cache line已被当前CPU修改过,内容和主存不一致;
  • 专有态(Exclusive):此cache line只存在当前CPU中,其它CPU缓存中没有,且和主存保持一致;
  • 共享态(Shared):此cache line存在多个CPU缓存中,且和主存保持一致;
  • 无效态(Invalid):此cache line已被其它CPU修改,导致当前CPU缓存中的数据无效;

cache line是CPU Cache管理数据最小单元,即CPU Cache和内存之间交换数据最小单位就是cache line,如果需要将某个变量加载到CPU中,会把该变量所处的cache line都统一一起加载进来,所以这里就引入了伪共享概念,即修改cache line中的一个变量导致处于该cache line中的其它变量也一起失效。

多处理器时,单个CPU对缓存中数据进行了改动,需要通知其它CPU,也就是意味着,CPU处理要控制自己读写操作,还需要监听其它CPU发出的通知,从而保证最终一致。

图片来源于网络

状态之间相互转换详细说明如下:

图片来源于网络

举个例子说明下,现在有个cache line位于CPU0CPU1中,所以,这个cache line状态是Shared共享态,现在CPU0需要对cache line中的一个变量进行修改,大致流程如下:

  • 修改前发现状态是S,这时就需要先发送invalidate失效通知给CPU1
  • CPU1收到invalidate失效通知后进行数据失效处理,将CPU1中对应的cache line状态标记为I,然后发送ackCPU0进行确认;
  • CPU0收到ack后,就开始对cache line中数据进行修改,修改完成后将cache line状态标记成M
  • 等到有其它CPU需要读取这个变量值时,CPU0将处于M状态的cache line同步到主存中,然后其它CPU就可以从主存中读取到变量最新值,这时cache line状态变成了S

store-buffer

上面分析了如何通过MESI协议解决CPU Cache的一致性问题,但是却存在性能问题。如红色框框标记这个区间内,CPU0是一直处于等待状态的,现在计算机CPU核数都比较多,可能要等所有的CPU核都返回ack确认消息后才能继续工作,造成CPU0资源被白白的浪费。

如何去解决MESI带来的CPU性能问题呢?这时候store-buffer就出场了。store-buffer是处于CPU核中的另一个缓存,当存在修改时,把修改直接放到store-buffer中,store-buffer后台异步方式发送invalidate通知到其它CPU以及处理ack确认等工作,这样CPU就可以不用傻傻等待了。

还以刚才场景为例,CPU0修改cache line后,直接丢给store-buffer,让store-buffer处理后续和其它CPU同步问题,自己可以接着干下面工作,store-buffer采用异步方式发送invalidate通知和处理ack,这样CPU0就不会存在长时间阻塞问题,提示了CPU性能。

内存屏障

store-buffer的引入虽然提升了CPU的性能,但是却引入了一个很大问题:数据不一致。CPU0中的cache line被修改后直接丢给store-bufferstore-buffer是异步处理方式,这时CPU0继续处理后续工作,其它CPUcache line由于还没有来得及通知可能还是旧数据,这就出现数据不一致问题。

比如下面代码可能存在这样一种场景:

  • CPU0执行cpu0()这个方法,首先将value值修改为10,假如value这个变量是S状态,其在CPU1中也存在;
  • CPU0执行完value修改后,将修改直接丢给store-buffer,然后执行isFinish = true,假如isFinish变量只有CPU0中有,其状态是E,然后修改后状态变成M
  • CPU1执行while(!isFinish)时,因为CPU1中没有变量isFinish,只有CPU0中有最新数据,这时CPU0会把自己缓存的isFinish刷新到主存中,然后CPU1从主存中读取到isFinish最新值true;继续向下执行assert value == 10,虽然CPU0已经把value设置成了10,但是可能CPU0store-buffer还没有发送出通知过来,导致CPU1value还是旧值3

上面分析场景来看:明明cpu0()方法中先执行value=10赋值,再去执行的isFinish=true赋值,但是在cpu1()方法中读取到了isFinish最新值,value却读到的是旧值。给人一种指令重排假象,这种就是伪指令重排,表面上像是发生了指令重排,实质上并没有进行指令重排,而是由于CPU缓存不一致造成的。

那怎么去解决这个问题呢?这里就引入了内存屏障。

cpu0()方法中两个语句中间插入一个内存屏障指令smp_mb(伪代码),该指令作用就是保住CPU0store-buffer中任务都同步完成后才能执行后续操作,也就保证CPU0上发生的修改对其它CPU都是可见的,然后再去执行后面语句。所以,这样就保证了CPU1中读取到isFinish最新值时,value也一定是最新值,从而解决了上面所说的问题。

invalidate-queues

内存屏障就是把store-buffer由异步执行变成同步执行的过程,store-buffer进行同步是个相当耗时的过程,需要发送invalidate通知到所有关联的CPU上,然后CPU接收到通知进行处理,处理完成后反馈ack,等获取到所有CPU反馈回来的ack才能继续向下执行。为了对内存屏障进行优化,又引入了invalidate queues(失效队列)概念。

如上图,store-bufferinvalidate通知发送到其它cpu,其它cpu接收到invalidate通知后放入到invalidate queues后直接反馈ack,因为处理invalidate也是比较耗时的工作,通过invalidate queues引入,缩短了store-buffer同步的时间。

读屏障、写屏障、全屏障

还是刚才那个场景,引入invalidate queues后,需要在cpu0()cpu1()两个方法中都插入一条内存屏障才能实现之前效果。

CPU0其实只需要把store-buffer同步出去即可,保证在cpu0()方法中的修改及时对其它CPU可见,插入内存屏障导致CPU0同时也会把invalidate queues处理掉,这是没有必要的一步;另一点,CPU1为了实现数据可见性,只需要把invalidate queues处理完就可以获取到value最新值,执行assert value == 10判断就没有问题了,插入内存屏障导致store-buffer中任务被处理同样是没必要的一步。

所以,对内存屏障进行优化,细分出三种类型:

  • 写屏障:主要用来保证store-buffer中的任务都被处理完成,才能继续后续操作,避免因指令重排导致的后续的写操作提前到这个写操作之前;
  • 读屏障:主要用于保证invalidate queues中的任务都被处理完成,才能继续后续操作;
  • 全屏障:同时保证store-bufferinvalidate queues中的任务都被处理完成才能继续后续操作;

所以,对上述代码优化后就是如下情形,只需要在cpu0方法中插入写屏障,cpu1方法中插入读屏障即可。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Reactor2020 微信公众号,前往查看

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

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • CPU Cache
  • MESI协议
  • store-buffer
  • 内存屏障
  • invalidate-queues
  • 读屏障、写屏障、全屏障
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档