GCC12似乎正在治疗
class
引用就像简单的引用一样
int *
,在是否
lhs
和
rhs
能够
部分
重叠
如果
lhs[idx]
与相同的int
rhs[idx]
,我们在写之前读了两遍。但有部分重叠,
rhs[3]
例如,可能已由其中一个更新
lhs[0..2]
添加,如果我们在任何存储之前先进行所有加载,SIMD就不会发生这种情况。
GCC13知道类对象不允许部分重叠(除了不同结构/类类型的常见初始序列内容,我认为这在这里不适用)。那将是UB,所以它可以假设它不会发生。GCC12的代码生成是一个遗漏的优化。
那么我们如何帮助GCC12呢?通常的做法是
__restrict
用于在编译器不想发明检查+回退时消除重叠检查或启用自动向量化。在C中,
restrict
是语言的一部分,但在C++中它只是一个扩展。(主要主流编译器支持,您可以使用预处理器
#define
将其转换为其他字符串上的空字符串。)您可以使用
限制
具有引用和指针。(至少GCC和Clang在没有警告的情况下接受了它
-Wall
; 我没有仔细检查文档以确保这是标准的。)
// downside: fn_restrict(same, same) would be UB
void fn_restrict(std::array<int, 4>&__restrict lhs, const std::array<int, 4>& rhs)
{
for (std::size_t idx = 0; idx != 4; ++idx) {
lhs[idx] = lhs[idx] + rhs[idx];
}
}
或者手动读取所有
lhs
在写任何东西之前
自从你
array
足够小,可以放在一个SIMD寄存器中,复制时不会有效率低下的问题。这对
array<int, 1000>
什么的!
// downside: only efficient for small arrays that fit in a few vector regs at most
void fn_temporary(std::array<int, 4>& lhs, const std::array<int, 4>& rhs)
{
auto sum = lhs; // read the possibly-aliasing data into a temporary
for (std::size_t idx = 0; idx != 4; ++idx) {
sum[idx] += rhs[idx]; // update the temporary
}
lhs = sum; // store back, after all loads
}
这两者都编译到与GCC13相同的自动矢量化asm,没有浪费指令(
Godbolt
)
# GCC12 -O3
fn_temporary(std::array<int, 4ul>&, std::array<int, 4ul> const&):
movdqu xmm0, XMMWORD PTR [rsi]
movdqu xmm1, XMMWORD PTR [rdi]
paddd xmm0, xmm1
movups XMMWORD PTR [rdi], xmm0
ret
承诺一致性(如
alignas(16)
其中一种?)可以让它使用
paddd xmm1, [rdi]
,内存源操作数,不带AVX。