为什么编译器会在每次循环迭代时将成员变量写入内存?

7

第一个版本通过将一个值从内存移动到一个本地变量中进行优化。第二个版本没有这样做。

我原以为编译器会在这里选择进行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获取完整的汇编代码和可供选择的编译器!

你所链接的示例还有一个问题。当第一次使用v1时,它是未初始化的。这是未定义行为,并会导致gcc和clang在优化时出现奇怪的问题。 - Richard Hodges
哦,是的 - 说得好 - 虽然这不是真正的代码,但我只是想展示我感兴趣的问题和v1的起点并不重要。实际的代码稍微精细了一点。 - JCx
有趣且值得警惕的阅读: https://blog.regehr.org/archives/759 - Richard Hodges
1个回答

11
由于别名的存在:v1是一个成员变量,x可能指向它。因此,对x元素的写入之一可能会更改v1
在C99中,您可以在指针类型的函数参数上使用restrict关键字,以通知编译器它不会与函数范围内的其他任何内容发生别名。一些C++编译器也支持它,尽管它不是标准。 (摘自我的评论之一。)

2
@JCx:在C99中,您可以在指针类型的函数参数上使用“restrict”关键字,以通知编译器它不会与函数作用域内的其他任何内容别名。一些C++编译器也支持它,尽管这不是标准。 - Aasmund Eldhuset
2
好的 - 使用clang测试过了。限制已经解决了。不错 :) - JCx
1
据我所知,出于不同的原因,它们被认为是邪恶的。我也看不出智能指针如何在这里有帮助。 - 463035818_is_not_a_number
1
我为这个工作感到非常兴奋... 这将使我们的过滤器代码变得更整洁。 - JCx
2
@mark - 参数不能指向局部变量,因为在函数调用时该局部变量并不存在。编译器真的很聪明! - Bo Persson
显示剩余12条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接