前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文讲明白内存重排序

一文讲明白内存重排序

作者头像
心平气和
发布2021-08-06 11:30:11
1.3K0
发布2021-08-06 11:30:11
举报
文章被收录于专栏:程序员升级之路

一、什么是内存重排序

重排序有指令重排序和内存重排序2种情况,指令重排序好理解,刚开始听到内存重排序的概念不是特别理解。

为了加深理解,举一个例子说明什么是内存重排序:

CPU0

CPU1

X=1 //S1

Y=1 //S2

r1=Y //S3

r2=X //S4

当前系统共2个CPU,CPU0和CPU1,上面是2个CPU执行的指令序列,其中X和Y为共享变量,r1和r2为局部变量。

上述CPU1执行S4处的指令后r2应该为1, r1应该为1,但有时间可能是r2为0,r1是1,即在CPU1看来好像S1的指令没有执行,但S3的指令已经执行了。这个不是由于指令排序造成的,是因为内存相关指令引起的,所以叫内存重排序。

二、发生内存重排序的原因

这个原因和CPU及其调整缓存相关,我们来看看整个CPU及缓存的架构变迁。

1、最初的架构

最早的CPU是没有做做任何优化,每次内存操作直接操作内存的。

2、加入Cache的架构

CPU执行指令的速度和访问内存指令速度是不匹配的,CPU执行速度太快,而访问内存的指令相对来说要慢些,为了加快快访问速度,硬件设计者们在每个CPU内部加入了高速缓存:

现在访问内存的流程是这样的:

先从高速缓存中读看有没有,如果没有则再从内存中读取,读到后再写入到高速缓存下次就可以命中缓存了。

这样在某些情况下访问内存速度确实加快了,但了带来了新的问题,如何保证各CPU高速缓存的数据一致性,即一个内存地址在每个CPU调整缓存中都有数据,现在某个CPU针对这个地址进行修改,怎么让其它CPU得到最新的数据?

为此设计者们引入了缓存一致性协议,这里不具体讨论这些协议的实现,说了大概的流程,加入协议之后,如果是一个写内存操作,必须通过通过总线广播一条消息我要修改某个地址了,然后让其它CPU更新或者删除自己的缓存,等所有其它CPU将自己缓存的数据更新后,才返回成功。

3、引入写缓冲及无效队列架构

前面讲了每个CPU加入自己的高速缓存后,如果是一个共享变量,整体性能会降低,那有没更快的办法呢,设计者们引入了写缓存:

截图引用自《Java多线程编程实践指南》,作者:黄文海

既然写内存是同步的,需要广播消息,能不能弄成异步的加快速度呢,写缓存队列就是干这个的,引入写缓存队列后写内存操作流程如下:

1)如果Cache中无数据,直接写入Cache,无需广播消息;

当然这里的Cache中无数据是每个Cpu的Cache都无数据,Cache一致性协议中有相关保障,有兴趣的同学可以学习下相关资料

2)如果Cache中有数据则写入写缓存器,然后就直接返回了

3)CPU再异步将写缓存器里的数据同步到其它CPU,这样就保证最终一致性了

这样可以加大写内存指令的速度了,因为不用广播消息,并且等待每个CPU的返回。

同样,这样也带来了新的问题,如果在写入指令写入高速缓存后,缓存的数据还没同步到其它CPU中,那其它CPU就有可能会读到脏数据。

现在我们回头来分析前面举的例子,为什么会发生这种情况:

CPU1执行到S4时,由于S1的执行结果可能还存留在写缓存中,因此CPU1无法感知到,所以执行S4的时候,CPU1读取到X的值还是未初始化的0。

怎么解决呢,Java有volatile关键字,加入这个关键字后,会在每次读取这个变量对应内存的时候,CPU都会发出一个清除缓存的指令,因而保证可以读取到最新的值。

三、总结

1、内存重排序实际上并不是真的相关操作被排序了,而是因为CPU引入缓存还没来得及刷新导致;

2、每个CPU都有自己的缓存,为了提高共享变量的写操作,CPU把整个操作变成异步的了,如果写入操作还没来的及同步到其它CPU,就有可能发生其它CPU读取到的是旧的值,因此看起来这条指令还没执行一样。

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

本文分享自 程序员升级之路 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档