CPU乱序执行-内存屏障

参考博客

为什么CPU会乱序执行

  1. 前后两条指令没有依赖;但是前一条指令访存地址不在L1 cache中,而后一条指令的访存地址在L1 cache中,因此可以先执行第二条指令,等待第一条指令将访存单元加载到L1 cache中再执行第1条指令。有些CPU是这样做的
    • 但是==X64==架构保证写操作按序完成【顺序开始、乱序执行、顺序完成】
      • 也保证读操作按序完成【顺序开始、乱序执行、顺序完成】
    • X64也不会将store乱序到之前的load前执行【同一个CPU】
    • ==!!== 但是:读可以被乱序到它之前的写之前进行 -> X64平台唯一可能会被重排的内存操作
      • store to ptr_2
      • load from ptr_1
      • 开始执行/完成执行都有可能乱序
      • 这里==后来的load有可能被乱序到store之前执行==
      • 当然CPU在这种情况下也不会无缘无故乱序:只有当前面的写操作还不具备执行、完成的条件时,才会跳过写,让后面的读先完成

考虑范围

  • 考虑内存屏障时,基本忽略了不同层级cache、内存之间的写/读延时

什么情况下会有问题

X64

  • 两个核均在短时间内执行【同一个核内前后两条/有限多条】指令进行
    • 写我的,读你的操作
    • 可能会有问题

MESI协议作为硬件层级的协议究竟保护了什么?

总结

  • 完成硬件级的cache同步
    • 但是同步有延迟,因此会带来一系列的问题 <- 需要使用内存屏障解决
    • 这里的==同步==概念与应用开发用到的同步有很大的不同?❌
      • 本质上是一样的,只是粒度不同
        • 我要读同一个地址(操作同一个对象)
        • 如果某些分支依赖于对象某些值
          • 但是在进入分支后,其它进/线程对该对象的该值进行了修改,那么当前线程后续的操作肯定会出大问题的
          • 而且这些操作离CPU太远了
    • 应用开发用到的锁/信号量等粗粒度同步机制的内部实现
      • 肯定会用到内存屏障
      • 例如自旋锁 -> 需要结合一个具体的实现仔细看一下
        • 都在自己的缓存中,lock_val = 0
        • core 0 尝试获取,将lock_val的值写为1。接着执行关键代码
          • 底层MESI协议开始起作用
          • 但是有一定延迟
        • 接着,core 1尝试获取,发现自己缓存中lock_val的值仍然为0,因此获取成功,也开始执行关键代码
      • 可以看到这里两个core都进入了关键区
      • 因此 core 0必须等待该写操作执行完成【写到内存中/或者MESI同步完成】才可以继续执行后续的指令
        • 因此 core 1 必须在读之前确保拿到被修改后的变量【必须从内存中获取】;即一定要使用内存屏障,确保这之前其它core做的修改均已写入到主存中

考虑场景

  • CPU0 写地址 ptr_a -> 对于CPU来说需要一个周期,之后接着执行后面的指令;但是该写入实际被写入到内存中还没有完成【即指令的完成时间还没到】
    • CPU1 同时读地址 ptr_a
  • 对于每个core,读写变量都是针对L1 cache的【当然要在开启cache的情况下讨论;此外某些MC U的某些地址位段有cache,而有些没有】

都在各自缓存中

  • 则标记为S
  • CPU0写之后会向另一个CPU发缓存失效信息 : 这里需要时间
    • CPU0写到自己的缓存后,就不管后续的操作了->只花费一个周期;接着执行后续的指令
      • 在之后的时间内,CPU的其它部件会完成剩余的工作,运行MESI协议
      • 因此前一个写指令实际完成的时间可能晚于后续的读【不同地址】操作
    • 在这段时间内,如果CPU1发起读操作怎么办?-> 如果CPU1那边发出的缓存失效信息还没有到达CPU1,那么CPU1会读到一个无效值!!
  • 这种由MESI协议同步延迟带来的不一致问题,也可以使用内存屏障来解决

如果两个Core真的同时访存同一个地址怎么办?

-


CPU乱序执行-内存屏障
http://example.com/2024/08/27/嵌入式-架构/CPU乱序执行-内存屏障/
作者
Cyokeo
发布于
2024年8月27日
许可协议