在这种情况下,为什么编译器要专门用于存储冗余变量的内存位置?

Why is the compiler dedicating a memory location for storing a redundant variable in this case?

wrote this简单的C++代码,看看原子变量是如何实现的。

#include <atomic>

using namespace std;

atomic<float> f(0);

int main() {
    f += 1.0;
}

正在为 -O3 中的 main 生成此程序集:

main:
        mov     eax, DWORD PTR f[rip]
        movss   xmm1, DWORD PTR .LC1[rip]
        movd    xmm0, eax
        mov     DWORD PTR [rsp-4], eax  ; this line is redundant
        addss   xmm0, xmm1
.L2:
        mov     eax, DWORD PTR [rsp-4]  ; this line is redundant
        movd    edx, xmm0
        lock cmpxchg    DWORD PTR f[rip], edx
        je      .L5
        mov     DWORD PTR [rsp-4], eax  ; this line is redundant
        movss   xmm0, DWORD PTR [rsp-4]  ; this line can become  movd    xmm0, eax
        addss   xmm0, xmm1
        jmp     .L2
.L5:
        xor     eax, eax
        ret
f:
        .zero   4
.LC1:
        .long   1065353216

它是使用原子比较和交换技术来实现原子性。但是在那里,旧值存储在 [rsp-4] 的堆栈中。但是在上面的代码中,eax 是不变的。所以旧值保留在 eax 本身中。为什么编译器要为旧值分配额外的 space?即使在-O3!是否有任何特定原因将该变量存储在堆栈中而不是寄存器中?

编辑: 逻辑推导 -

有4行使用rsp-4 -

mov     DWORD PTR [rsp-4], eax    --- 1
mov     eax, DWORD PTR [rsp-4]    --- 2  <--.
mov     DWORD PTR [rsp-4], eax    --- 3     | loop
movss   xmm0, DWORD PTR [rsp-4]   --- 4  ---'

第 3 行和第 4 行之间绝对没有其他内容,因此 4 可以使用 3 重写为
movd xmm0, eax.

现在,在循环中从第 3 行到第 2 行时,没有对 rsp-4(也没有 eax)的修改。所以这意味着第 3 行和第 2 行按顺序一起折叠为
mov eax, eax
这本质上是多余的。

最后,只剩下第 1 行,其目的地不再使用。所以也是多余的。

Is there any specific reason to store that variable in stack rather than in registers?

归根结底,线程间通信存在原子性,不能跨线程共享寄存器。

您可能认为 gcc 可以 检测从未与其他任何东西共享的局部变量原子并将它们降级为常规变量。然而:

  1. 我个人看不出这会给 table 带来什么,因为在这些情况下你不应该使用原子。
  2. 该标准似乎无论如何都禁止这种优化:

intro.races-14

The value of an atomic object M, as determined by evaluation B, shall be the value stored by some side effect A that modifies M, where B does not happen before A.

这里的关键词是side effect,意思是对实际内存存储的修改无可厚非。它必须发生。

就修改后的问题而言:

But in the above code, eax is invariant

不幸的是不是。 cmpxchg 读取和写入 eax,因此需要在循环的每次迭代中重新分配它。

需要循环,因为为了在原子浮点数上执行 += 1。编译器必须不断尝试,直到它设法以足够快的速度执行读-增量-写序列,以至于原子在此期间不会发生变化。