【问题标题】:Instruction reordering on intel英特尔上的指令重新排序
【发布时间】:2019-01-15 16:04:33
【问题描述】:

我试图通过以下简单示例来理解指令重新排序:

int a;
int b;

void foo(){
   a = 1;
   b = 1;
}

void bar(){
   while(b == 0) continue;
   assert(a == 1);
}

众所周知,在这个例子中,如果一个线程执行foo,而另一个线程执行bar,则断言可能会失败。但我不明白为什么。我咨询了Intel manual Vol. 3A, 8.2.2,发现如下:

对内存的写入不会与其他写入重新排序,与 以下例外:

— 使用 非临时移动指令(MOVNTI、MOVNTQ、MOVNTDQ、MOVNTPS 和 MOVNTPD);和

——字符串操作(参见第 8.2.4.1 节)。

这里没有字符串操作,我也没有注意到NT 移动指令。那么...为什么可以对写入进行重新排序?

或者说记忆在中重要吗

写入内存不会重新排序

?因此,当我们缓存了 ab 并且写入不会发生在主内存中,而是发生在缓存中时。

【问题讨论】:

  • 为什么你认为ab会被存储在内存中?在 C++ 语言中没有这样的要求,在极端情况下,编译器可能会生成将这两个仅保留在寄存器中的代码(例如)。
  • @Ped7g 是的,我问错了问题......对不起
  • 没问题,搜索正确的问题是该过程的一部分,有时该过程需要像这样的步骤。 :) 但是这个话题可能很棘手,所以你必须更精确才能得到一些有意义的答案..
  • 您需要在此处防止编译器重新排序。说在 x86/x64 上插入 a = 1; _ReadWriteBarrier(); b = 1; 就足够了
  • 这实际上是 C 还是 C++,或者您实际上是在询问按该顺序执行这些操作的程序集?因为显然这是 C 中的 UB,并且 C 内存模型适用于编译时重新排序和负载提升,而不是 x86 内存模型。 IIRC,您在上一个问题中这样做了,浪费了每个人的时间来整理 C 与 x86 内存模型,所以请在问题中说明您的实际意思是 asm 是否看起来像这样。显然while(b == 0){}如果它完全进入循环,任何普通的 C 编译器都会变成无限循环。

标签: c assembly x86 memory-barriers


【解决方案1】:

如果一个线程正在运行 foo 而另一个正在运行 bar,那么您的程序的行为将是未定义

你不能同时读写一个非原子变量,例如int

所以在这种情况下指令重新排序是允许的。

【讨论】:

  • @RbMm 如果相应对齐...但无论如何都是UB。
  • 是的,同意对齐。但在这种情况下,我没有查看代码中覆盖默认对齐方式的任何编译指示。所以假设int的自然对齐。在这种情况下,不要查看任何 ub。当然需要强制编译器不重新排序a=1,b=1
【解决方案2】:

你的前提是错误的。只有编译时重新排序才能在 x861 上破坏此示例。

x86 asm 存储是发布存储。它们只能按程序顺序从存储缓冲区提交到 L1d 缓存。

a 不能在b=1 可见后仍然处于共享状态;这意味着运行foo 的线程会让它的存储乱序提交。这就是写入内存不会与其他写入重新排序的意思,用于存储到可缓存的内存。

如果它在被运行foo 的线程中的RFO 无效后再次处于共享状态,那么它将具有a 的更新值。


脚注 1. 当然,自旋循环将优化为if (b==0) infinite_loop,因为数据竞争 UB 让编译器提升负载。见MCU programming - C++ O2 optimization breaks while loop

您似乎在询问 C 规则,同时假设代码将被天真/直接转换为 x86 asm。您可以通过轻松的原子来获得它,但不是 volatile,因为 volatile 访问不能与其他 volatile 访问重新排序(在编译时)。

【讨论】:

猜你喜欢
  • 2019-08-02
  • 2023-03-06
  • 2020-05-12
  • 2012-11-13
  • 1970-01-01
  • 2012-01-01
  • 1970-01-01
  • 2018-03-17
  • 2011-05-10
相关资源
最近更新 更多