尽管存在__restrict关键字,但将成员变量移动到局部变量会减少此循环中的写入次数。这是使用GCC -O3。 Clang和MSVC在两种情况下都优化写入。 [请注意,由于此问题已发布,我们发现将__restrict添加到调用函数会导致GCC也将商店移出循环。请参阅下面的godbolt链接和评论]
class X
{
public:
void process(float * __restrict d, int size)
{
for (int i = 0; i < size; ++i)
{
d[i] = v * c + d[i];
v = d[i];
}
}
void processFaster(float * __restrict d, int size)
{
float lv = v;
for (int i = 0; i < size; ++i)
{
d[i] = lv * c + d[i];
lv = d[i];
}
v = lv;
}
float c{0.0f};
float v{0.0f};
};
使用gcc -O3,第一个内部循环看起来像:
.L3:
mulss xmm0, xmm1
add rdi, 4
addss xmm0, DWORD PTR [rdi-4]
movss DWORD PTR [rdi-4], xmm0
cmp rax, rdi
movss DWORD PTR x[rip+4], xmm0 ;<<< the extra store
jne .L3
.L1:
rep ret
第二个在这里:
.L8:
mulss xmm0, xmm1
add rdi, 4
addss xmm0, DWORD PTR [rdi-4]
movss DWORD PTR [rdi-4], xmm0
cmp rdi, rax
jne .L8
.L7:
movss DWORD PTR x[rip+4], xmm0
ret
有关完整代码,请参阅https://godbolt.org/g/a9nCP2。
为什么编译器不在这里执行lv优化?
我假设每个循环的3个内存访问比2更差(假设大小不是一个小数字),虽然我还没有测量过。
我做出这个假设是对的吗?
我认为两种情况下的可观察行为都应该相同。
答案 0 :(得分:3)
这似乎是由__restrict
函数上缺少f_original
限定符引起的。 __restrict
is a GCC extension;目前尚不清楚如何在C ++中表现出来。也许这是一个编译器错误(错过优化),它似乎在内联后消失。
答案 1 :(得分:0)
这两种方法并不相同。在第一个中,v
的值在执行期间被多次更新。这可能是你想要的,也可能不是你想要的,但它与第二种方法不同,所以编译器不能将自己定义为可能的优化。
答案 2 :(得分:0)
restrict关键字表示没有其他任何别名,实际上就像值是本地的一样(并且没有本地对它有任何引用)。
在第二种情况下,v
没有外部可见效果,因此不需要存储它。
在第一种情况下,某些外部可能会看到它,编译器此时并不知道没有可以改变它的线程,但它知道它不必读取它因为它既不原子也不易变。而d[]
另一个外部可见变量的更改使得存储成为必要。
如果编译器编写者推理,那么d
和v
都不是易失性的,也不是原子的,所以我们可以使用'as-if'来完成所有这些,然后编译器必须确保没有人可以完全触摸v
。我很确定这会出现在新版本之一,因为在返回之前没有同步,所有情况下99%的情况都是如此。然后,程序员必须将volatile或atomic放在已更改的变量上,我认为我可以使用它。