为什么编译器为此循环的每次迭代都将一个成员变量写入内存?

问题描述:

第一个版本通过将值从内存移到局部变量来进行优化。第二个版本没有。为什么编译器为此循环的每次迭代都将一个成员变量写入内存?

我在期待编译器可能会选择在这里进行localValue优化,而不是每次循环读取和写入内存值。为什么不呢?

class Example 
{ 
    public: 
     void processSamples(float * x, int num) 
     { 
      float localValue = v1; 

      for (int i = 0; i < num; ++i) 
      { 
       x[i] = x[i] + localValue; 
       localValue = 0.5 * x[i]; 
      } 

      v1 = localValue; 
     } 

     void processSamples2(float * x, int num) 
     { 

      for (int i = 0; i < num; ++i) 
      { 
       x[i] = x[i] + v1; 
       v1 = 0.5 * x[i]; 
      } 

     } 

    float v1; 
}; 

processSamples组装到这样的代码:

.L4: 
    addss xmm0, DWORD PTR [rax] 
    movss DWORD PTR [rax], xmm0 
    mulss xmm0, xmm1 
    add rax, 4 
    cmp rax, rcx 
    jne .L4 

processSamples2这样:

.L5: 
    movss xmm0, DWORD PTR [rax] 
    addss xmm0, DWORD PTR example[rip] 
    movss DWORD PTR [rax], xmm0 
    mulss xmm0, xmm1 
    movss DWORD PTR example[rip], xmm0 
    add rax, 4 
    cmp rax, rdx 
    jne .L5 

当编译器不必担心螺纹(V1不是原子) 。难道它不能只是假设没有别的东西会看着这个值,并继续在循环旋转时将它保存在寄存器中?

请参阅https://godbolt.org/g/RiF3B4以获得完整的装配和编译器供您选择!

+0

您链接的示例存在另一个问题。首次使用时,“v1”未初始化。这是UB,会导致gcc和clang在优化时间做出怪异的事情。 –

+0

哦,是的 - 公平点 - 这不是真正的代码,虽然我只是想展示我感兴趣的问题,v1的出发点并不重要。实际的代码更精致一点。 – JCx

+0

有趣和警示阅读: https://blog.regehr.org/archives/759 –

由于aliasingv1是一个成员变量,它可能是x指向它。因此,写入x元素之一可能会更改v1

在C99中,您可以在指针类型的函数参数上使用restrict关键字来通知编译器它不会别名在函数范围内的其他任何别名。一些C++编译器也支持它,尽管它不是标准的。 (复制自我的评论之一)

+0

哇 - 好的 - 那么有没有告诉它的方式不会是这样? – JCx

+2

@JCx:在C99中,可以在指针类型的函数参数中使用'restrict'关键字来通知编译器它不会别名在函数范围内的其他任何别名。 [一些C++编译器也支持它](https://*.com/questions/776283/what-does-the-restrict-keyword-mean-in-c#1965502),尽管它不是标准的。 –

+2

好吧 - 用叮叮进行测试。限制整理出来。尼斯:) – JCx