循环外写的优化

Optimisation of a write out of a loop

本文关键字:优化 循环      更新时间:2023-10-16

尽管存在__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次更糟糕(假设大小不是一个小数字),尽管我还没有测量到这一点。

我这样做对吗?

我认为在这两种情况下,可观察到的行为应该是相同的。

这似乎是由f_original函数上缺少__restrict限定符引起的。CCD_ 3是GCC的扩展;目前还不太清楚它在C++中的表现。也许是编译器错误(错过了优化)导致它在内联后消失。

这两种方法不完全相同。在第一种情况下,v的值在执行期间被多次更新。这可能是也可能不是你想要的,但它与第二种方法不同,因此编译器不能自行决定是否进行优化。

restrict关键字表示没有与其他任何东西的别名,实际上就像该值是本地的一样(并且没有本地对它的任何引用)。

在第二种情况下,v没有外部可见效果,因此不需要存储

在第一种情况下,某些外部可能会看到它,编译器此时并不知道不会有线程可以更改它,但它知道不必将其读取为既不是原子性的也不是易失性的。而d[]的变化又是另一个外部可见的变量,使得存储成为必要。

如果编译器编写者进行推理,那么dv都不是易失性的,也不是原子性的,所以我们可以使用"好像"来完成这一切,那么编译器必须确保没有人能接触到v。我很确定这将出现在新版本中,因为在返回之前没有同步,无论如何,99%以上的情况都是这样。然后,程序员将不得不将volatile或atomic放在更改的变量上,我认为我可以接受这一点。