为什么编译器为此循环的每次迭代都将一个成员变量写入内存?
问题描述:
第一个版本通过将值从内存移到局部变量来进行优化。第二个版本没有。为什么编译器为此循环的每次迭代都将一个成员变量写入内存?
我在期待编译器可能会选择在这里进行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以获得完整的装配和编译器供您选择!
答
由于aliasing:v1
是一个成员变量,它可能是x
指向它。因此,写入x
元素之一可能会更改v1
。
在C99中,您可以在指针类型的函数参数上使用restrict关键字来通知编译器它不会别名在函数范围内的其他任何别名。一些C++编译器也支持它,尽管它不是标准的。 (复制自我的评论之一)
您链接的示例存在另一个问题。首次使用时,“v1”未初始化。这是UB,会导致gcc和clang在优化时间做出怪异的事情。 –
哦,是的 - 公平点 - 这不是真正的代码,虽然我只是想展示我感兴趣的问题,v1的出发点并不重要。实际的代码更精致一点。 – JCx
有趣和警示阅读: https://blog.regehr.org/archives/759 –