编程高手必学的内存知识
海纳
华为编译器高级专家,原 Huawei JDK 团队负责人
20674 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 33 讲
编程高手必学的内存知识
15
15
1.0x
00:00/00:00
登录|注册

16 | 内存模型:有了MESI为什么还需要内存屏障?

你好,我是海纳。
上一节课,我们学习了 MESI 协议,我们了解到,MESI 协议能够解决多核 CPU 体系中,多个 CPU 之间缓存数据不一致的问题。但是,如果 CPU 严格按照 MESI 协议进行核间通讯和同步,核间同步就会给 CPU 带来性能问题。既要遵守协议,又要提升性能,这就对 CPU 的设计人员提出了巨大的挑战。
那严格遵守 MESI 协议的 CPU 会有什么样的性能问题呢?我们又可以怎么来解决这些问题呢?今天我们就来仔细分析一下。搞清楚了这些问题,你会对 C++ 内存模型和 Java 内存模型有更加深入的理解,在分析并发问题时能够做到有的放矢。

严守 MESI 协议的 CPU 会有啥问题?

我们上节课说过,MESI 代表的是 Modified、Exclusive、Shared、Invalid 这四种缓存状态,遵守 MESI 协议的 CPU 缓存会在这四种状态之间相互切换。这种 CPU 缓存之间的关系是这样的:
从上面这张图你可以看到,Cache 和主内存 (Memory) 是直接相连的。一个 CPU 的所有写操作都会按照真实的执行顺序同步到主存和其他 CPU 的 cache 中。
严格遵守 MESI 协议的 CPU 设计,在它的某一个核在写一块缓存时,它需要通知所有的同伴:我要写这块缓存了,如果你们谁有这块缓存的副本,请把它置成 Invalid 状态。Invalid 状态意味着该缓存失效,如果其他 CPU 再访问这一缓存区时,就会从主存中加载正确的值。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

多核CPU之间的缓存一致性问题一直是一个重要挑战,而MESI协议和store buffer结构在解决这一问题中发挥了关键作用。然而,严格遵守MESI协议可能会影响CPU性能,因此引入了弱缓存一致性和内存屏障来解决缓存顺序一致性的问题。此外,失效队列和读写屏障的引入进一步提升了CPU的响应速度和缓存顺序一致性。文章还介绍了单向屏障的概念,以及在不同体系结构下的应用。这些技术特点对于理解并发问题和内存模型具有重要意义。 在CPU的具体实现中,通过放宽MESI协议的限制来获得性能提升。具体来说,引入了store buffer和invalid queue,它们采用放宽MESI协议要求的办法,提升了写缓存核间同步的速度,从而提升了程序整体的运行速度。然而,放宽协议也带来了新的问题,即一个CPU的读写操作在其他CPU看来出现了乱序,需要加入内存屏障来解决这个问题。最后,学习了读写屏障分离和单向屏障,以及如何正确地使用内存屏障。 文章还提出了一个思考题,讨论了在Java代码中使用fullFence是否合理,以及可能的替代方案。除此之外,还提示了使用volatile关键字的改法,并鼓励读者在留言区分享想法。 总的来说,本文深入探讨了多核CPU之间的缓存一致性问题以及相关解决方案,对于对MESI协议和内存屏障感兴趣的读者具有很高的参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《编程高手必学的内存知识》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(24)

  • 最新
  • 精选
  • sc
    CPU 从单核发展为多核,增加缓存,导致出现了多个核间的缓存一致性问题 --> 为了解决缓存一致性问题,提出了 MESI 协议 --> 完全遵守 MESI 又会给 CPU 带来性能问题 --> CPU 设计者又增加 store buffer 和 invalid queue --> 又导致了缓存的顺序一致性变为了弱缓存一致性 --> 需要缓存的顺序一致性的,就需要软件工程师自己在合适的地方添加内存屏障

    作者回复: 总结得很好呀。这就是整个硬件篇的内在逻辑。感动~

    2021-12-10
    2
    23
  • .
    我看了一些文献关于X86(total store ordering )加上自己的理解: 1)由于不存在invalidate queue且读和读之间不会重排序切读线程会先读取store buffer,因此LoadLoad屏障在x86是无用的 2)由于store buffers是采用队列且不同地址的写是不会重排序因此StoreStore 可不存在 3)读可以与之前同位置排序,但不能与非同位置重排序(我在wiki搜不到这个屏障多线程问题,关于这个多线程实际应用场景不明确) LoadStore也没有了意义 4)但是写可能与之前的读重排序因此需要特别考虑这种重排序,StoreLoad存在意义 且存在可行性问题(但是老师说TSO不存在缓存一致性我不大理解这个在StoreLoad是否也是成立)

    作者回复: 赞!这就学得很深入了。我这里简化了描述,TSO模型唯一需要的是StoreLoad barrier,但它产生的原因不是缓存一致性。 所以x86上遇到需要StoreLoad barrier的地方,还要是正确添加才可以。

    2021-12-06
    5
    8
  • 一塌糊涂
    必须赞,看过很多资料,唯一能讲明白的!大神推荐点资料吧。

    作者回复: 多谢。这些内容是从很多文章里以及CPU源文件,还有向CPU设计者请教得到的心得。所以,我一下子也很难说得上来有什么资料。我觉得这个专栏本身可能就是目前的中文互联网上的一个比较好的资料吧。至于说这一节课的内容,核心思想是来自这篇文章:《Memory Barriers: a Hardware View for Software Hackers》,但我并不建议你去看这篇文章,因为这篇文章里在讲到MESI状态变化的时候过于复杂了。

    2021-12-03
    5
  • shenglin
    思考题实例代码使用fullFence()正确性是可以保证的,只是性能下降? // CPU0 void foo() { a = 1; unsafe.storeFence(); b = 1; } // CPU1 void bar() { while (b == 0) continue; unsafe.loadFence(); assert(a == 1); } 可以改成这样?因为由于store buffer的存在,foo()函数要a的新值写入缓存之后,b的新值才能写入,所以要使用storeFence屏障; bar()函数要读取b和a的新值,且由于invalid_queue的存在,要加入loadFence保证读取到a的新值前invalid_queue的消息已经被处理完成,即将CPU1的缓存中的a值更新。这样即保证了正确性,又兼顾了性能,不知道这样理解对不对?

    作者回复: 完全正确!非常好,说明你真的掌握了!

    2021-12-03
    3
  • 一个工匠
    在正常的程序中,多个 CPU 一起操作同一个变量的情况是比较少的,所以 store buffer 可以大大提升程序的运行性能。 这一块不是很理解,单个cpu操作同一个变量不就不需要mesi了么?所以mesi就是处理多核数据读写不一致问题而存在的,并且可以解决。但是优化之后,又不可以解决了,并且要让开发人员添加屏障进一步解决。那是不是可以理解mesi最后还是没完成“多核缓存一致性”工作?

    作者回复: mesi是一个协议。cpu的设计者完全遵守协议就一定能保证数据一致性。但是完全遵守协议性能低,这时候就有人想,我放松一点要求,性能就可以得到很大的提升,但需要软件工程师帮忙。总的看来这种放松是有利的。

    2022-02-14
    2
    1
  • 陈狄
    请问老师,「内存屏障的作用是屏障前面的读写未完成,不会进行屏障后面的读写」,怎么判断store buffer,尤其是invalid queue里的数据是屏障前面未完成的读写?

    作者回复: cpu会保证的,当遇到dmb指令的时候,cpu会停下来,直到条件满足了才会继续执行。

    2022-01-09
    1
  • Vvin
    你好!请问:如果在有store buffer的情况!cup0 and cpu1都写同一个状态为shared的a变量,怎么处理?

    作者回复: 总线会做裁决。总线最后看到的是谁就用谁的值。所以会有一定的随机性。

    2021-12-12
    1
  • BlockQuant
    “CPU0 在得到这个确认消息以后,就可以独占该缓存了,直接将这块缓存变为 Modified 状态,然后把 a 写入。在 a 写入以后,foo 函数中的内存屏障就可以顺利通过了,接下来就可以写入变量 b 的新值。由于 b 是 Exclusive 的” 这个 b 为啥是 E 状态,CPU1也在用,不应该是 S 吗?

    作者回复: 哦。这是我们假设的一种状态,比如线程1所在的CPU里早就有了b的缓存,而线程2还没得到调度,它的CPU里没有b的缓存。这几种假设状态都是有可能的。所以我们写的代码并不是一定出问题,而是有概率出问题。

    2021-12-07
  • 那时刻
    请问老师,内存屏障对于其它语言,比如go,是否是一致的呢? 对于其它语言,读写屏障可以保证代码的执行顺序是一致的

    作者回复: 这节课的内容其实和语言无关。内存屏障都是需要的,只是看这门语言是以什么样的api暴露给用户。

    2021-12-07
  • dog_brother
    老师,这里说的内存屏障跟c++11的memory order是一回事么?

    作者回复: acquire/release是一样的。

    2021-12-06
收起评论
显示
设置
留言
24
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部