从理论上讲,自从 Nehalem 以来,英特尔处理器上的对齐应该无关紧要。因此,您的编译器应该能够生成指针是否对齐不是问题的代码。
自 Nehalem 以来,未对齐的加载/存储指令在 Intel 处理器上的性能相同。然而,在 AVX 与 Sandy Bridge 一起到达之前,未对齐的负载无法通过另一个操作折叠以进行微操作融合。
此外,即使在 AVX 之前避免使用 16 字节对齐内存的高速缓存行拆分的惩罚仍然会有所帮助,因此编译器在指针对齐 16 字节之前添加代码仍然是合理的。
由于 AVX,使用对齐的加载/存储指令不再具有优势并且编译器没有理由添加代码以使指针 16 字节或 32 字节对齐。。
但是,直到有理由使用对齐内存来避免 AVX 的缓存行拆分。因此,编译器添加代码以使指针 32 字节对齐是合理的,即使它仍然使用未对齐的加载指令。
所以在实践中,当一些编译器被告知假设指针是对齐的时,它们会生成更简单的代码。
我不知道有一种方法可以告诉 MSVC 指针已对齐。使用 GCC 和 Clang(自 3.6 起),您可以使用内置的 __builtin_assume_aligned。使用 ICC 和 GCC,您可以使用 #pragma omp simd aligned。使用 ICC,您还可以使用 __assume_aligned。
例如用 GCC 编译这个简单的循环
void foo(float * __restrict a, float * __restrict b, int n)
{
//a = (float*)__builtin_assume_aligned (a, 16);
//b = (float*)__builtin_assume_aligned (b, 16);
for(int i=0; i<(n & (-4)); i++) {
b[i] = 3.14159f*a[i];
}
}
gcc -O3 -march=nehalem -S test.c 然后wc test.s 给出 160 行。而如果使用 __builtin_assume_aligned 那么 wc test.s 只给出 45 行。当我在这两种情况下都使用 clang 返回 110 行时。
因此,当叮当声通知编译器数组对齐时没有任何区别(在这种情况下),但使用 GCC 确实如此。计算代码行数并不足以衡量性能,但我不会在此处发布所有程序集,我只是想说明您的编译器在被告知数组对齐时可能会生成非常不同的代码。
当然,GCC 因不假设数组是对齐的而产生的额外开销在实践中可能没有什么不同。您必须测试并查看。
无论如何,如果您想从 SIMD 中获得最大收益,我不会依赖编译器来正确完成它(尤其是使用 MSVC)。您的matrix*vector 示例总体上很差(但可能不适用于某些特殊情况),因为它受内存带宽限制。但是如果你选择matrix*matrix,没有很多不符合 C++ 标准的帮助,任何编译器都无法很好地优化它。在这些情况下,您将需要内在函数/内置函数/程序集,无论如何您都可以显式控制对齐方式。
编辑:
来自 GCC 的程序集包含许多不属于文本段的无关行。执行gcc -O3 -march=nehalem -S test.c 然后使用objdump -d 并计算文本(代码)段中的行数给出108 行而不使用__builtin_assume_aligned 并且只有16 行。这更清楚地表明,当 GCC 假定数组是对齐的时,它会产生非常不同的代码。
编辑:
我继续在 MSVC 2013 中测试了上面的 foo 函数。它产生未对齐的负载,并且代码比 GCC 短得多(我这里只显示主循环):
$LL3@foo:
movsxd rax, r9d
vmulps xmm1, xmm0, XMMWORD PTR [r10+rax*4]
vmovups XMMWORD PTR [r11+rax*4], xmm1
lea eax, DWORD PTR [r9+4]
add r9d, 8
movsxd rcx, eax
vmulps xmm1, xmm0, XMMWORD PTR [r10+rcx*4]
vmovups XMMWORD PTR [r11+rcx*4], xmm1
cmp r9d, edx
jl SHORT $LL3@foo
这在 Nehalem(2008 年末)以来的处理器上应该没问题。但是即使我告诉编译器它是 4 的倍数 ((n & (-4)),MSVC 仍然有针对不是 4 倍数的数组的清理代码。至少 GCC 做对了。
由于 AVX 可以折叠 unalinged 负载,因此我使用 AVX 检查了 GCC 以查看代码是否相同。
void foo(float * __restrict a, float * __restrict b, int n)
{
//a = (float*)__builtin_assume_aligned (a, 32);
//b = (float*)__builtin_assume_aligned (b, 32);
for(int i=0; i<(n & (-8)); i++) {
b[i] = 3.14159f*a[i];
}
}
没有__builtin_assume_aligned GCC 会产生 168 行汇编,使用它只会产生 17 行。