众所周知,GCC/CLang 使用 SIMD 指令可以很好地自动向量化循环。
还已知存在对齐()标准 C++ 属性,除其他用途外还允许对齐堆栈变量,例如以下代码:
在线尝试一下!
#include <cstdint>
#include <iostream>
int main() {
alignas(1024) int x[3] = {1, 2, 3};
alignas(1024) int (&y)[3] = *(&x);
std::cout << uint64_t(&x) % 1024 << " "
<< uint64_t(&x) % 16384 << std::endl;
std::cout << uint64_t(&y) % 1024 << " "
<< uint64_t(&y) % 16384 << std::endl;
}
Outputs:
0 9216
0 9216
这意味着两者x
and y
在堆栈上按 1024 字节对齐,但不是 16384 字节。
现在让我们看另一个代码:
在线尝试一下!
#include <cstdint>
void f(uint64_t * x, uint64_t * y) {
for (int i = 0; i < 16; ++i)
x[i] ^= y[i];
}
如果编译时使用-std=c++20 -O3 -mavx512f
GCC 上的属性它会生成以下 asm 代码(提供部分代码):
vmovdqu64 zmm1, ZMMWORD PTR [rdi]
vpxorq zmm0, zmm1, ZMMWORD PTR [rsi]
vmovdqu64 ZMMWORD PTR [rdi], zmm0
vmovdqu64 zmm0, ZMMWORD PTR [rsi+64]
vpxorq zmm0, zmm0, ZMMWORD PTR [rdi+64]
vmovdqu64 ZMMWORD PTR [rdi+64], zmm0
AVX-512 未对齐加载 + 异或 + 未对齐存储执行两次。所以我们可以理解,我们的 64 位数组异或操作被 GCC 自动向量化以使用 AVX-512 寄存器,并且循环也被展开。
我的问题是如何告诉 GCC 提供给函数指针x
and y
都对齐到 64 字节,因此而不是未对齐的负载 (vmovdqu64
)就像上面的代码一样,我可以强制 GCC 使用对齐负载 (vmovdqa64
)。众所周知,对齐的加载/存储可以快得多。
我第一次尝试强制 GCC 进行对齐加载/存储是通过以下代码:
在线尝试一下!
#include <cstdint>
void g(uint64_t (&x_)[16],
uint64_t const (&y_)[16]) {
alignas(64) uint64_t (&x)[16] = x_;
alignas(64) uint64_t const (&y)[16] = y_;
for (int i = 0; i < 16; ++i)
x[i] ^= y[i];
}
但这段代码仍然会产生未对齐的负载(vmovdqu64
)与上面的 asm 代码(之前的代码片段)相同。因此这个alignas(64)
提示没有提供任何有用的信息来改进 GCC 汇编代码。
我的问题是如何强制 GCC 进行对齐的自动矢量化,除了为所有操作手动编写 SIMD 内在函数,例如_mm512_load_epi64()
?
如果可能的话,我需要所有 GCC/CLang/MSVC 的解决方案。