for 循环的 MMX SSE 扩展
MMX SSE extensions for for loop
我有 GCC 9.2 编译器。
如果我使用 MMX 或 SSE/AVX 扩展,您将并行编码 运行,因此它会更快。
如何告诉编译器使用这个指令
我有一个要并行的代码片段:
char max(char * a, int n){
char max = (*a);
for (int i = 0 ; i< n ; ++i){
if (max < a[i]){
max = a[i];
}
}
return max;
}
它使用 SSE 扩展生成代码但不使用 pmaxub
为什么
SSE2 是 x86-64 的基准,所以 pmaxub
可用。
但是您的代码在 x86-64 系统 V ABI 中使用 char
,并且 char
= signed char
,并且 Windows x64。也许您来自 char
= unsigned char
的 ARM? ISO C 标准保留了 char 实现定义的符号性,因此依赖它来确保正确性(或在这种情况下的性能)是一个糟糕的主意。
如果你像普通人一样使用 uint8_t
,即使没有使用 -march=skylake
或任何启用的东西,你也会从 GCC9.2 -O3
获得 x86-64 的预期内循环AVX2。 (Godbolt)
.L14:
movdqu xmm2, XMMWORD PTR [rax]
add rax, 16
pmaxub xmm0, xmm2
cmp rax, rdx
jne .L14
pmaxsb
requires SSE4.1。 (SSE2 与 MMX 一样是高度非正交的,一些操作仅适用于某些大小和符号组合,针对特定应用程序,如音频 DSP 和图形像素。SSE4.1 填补了许多空白。)
如果启用它,GCC 和 clang 将使用它。
仅使用 -O3
和基线 x86-64 -march
默认值(和 -mtune=generic
),GCC 使用 pcmpgtb
自动矢量化(这是一个带符号的比较)然后使用 pand
/pandn
/por
和必要的额外 movdqa
复制进行手动混合。 pcmpgtb
提示您编写的代码需要签名比较,而不是未签名。 Clang 做同样的事情。
.L5:
movdqu xmm1, XMMWORD PTR [rax]
add rax, 16
movdqa xmm2, xmm1
pcmpgtb xmm2, xmm0
pand xmm1, xmm2
pandn xmm2, xmm0
movdqa xmm0, xmm2
por xmm0, xmm1
cmp rax, rdx
jne .L5
GCC 可以通过 pmaxub
的范围移位输入自动矢量化为无符号,然后通过 adding/subtracting 128.(即 pxor
和 _mm_set1_epi8(0x80)
)。因此,对于这种情况,这是一个很大的优化失误,它本来可以将关键路径延迟降低到 1 个周期,只是 pmaxub
。
当然,如果您实际启用 SSE4.1,您会得到 pmaxsb
。或者 AVX2 vpmaxsb
.
您 可以 使用 -msse4.1
或 -mavx2
但通常您希望启用更新的 CPU 也有的其他扩展,并设置调整设置。特别是对于 AVX2,您不想针对 Sandybridge 和更旧的 CPU 进行调整,因为 SnB 甚至 没有 AVX2。您不希望拆分未对齐的负载和类似的东西。此外,AVX2 CPUs 通常也有 BMI2、popcnt 和其他好东西。
使用-march=haswell
或-march=znver1
(禅)。或者对于本地使用,-march=native
来优化您的 CPU。 (如果你有 Skylake,它与使用 -march=skylake
相同,除非它可能检测到你的特定 L3 缓存大小或其他东西。)
我有 GCC 9.2 编译器。 如果我使用 MMX 或 SSE/AVX 扩展,您将并行编码 运行,因此它会更快。 如何告诉编译器使用这个指令 我有一个要并行的代码片段:
char max(char * a, int n){
char max = (*a);
for (int i = 0 ; i< n ; ++i){
if (max < a[i]){
max = a[i];
}
}
return max;
}
它使用 SSE 扩展生成代码但不使用 pmaxub 为什么
SSE2 是 x86-64 的基准,所以 pmaxub
可用。
但是您的代码在 x86-64 系统 V ABI 中使用 char
,并且 char
= signed char
,并且 Windows x64。也许您来自 char
= unsigned char
的 ARM? ISO C 标准保留了 char 实现定义的符号性,因此依赖它来确保正确性(或在这种情况下的性能)是一个糟糕的主意。
如果你像普通人一样使用 uint8_t
,即使没有使用 -march=skylake
或任何启用的东西,你也会从 GCC9.2 -O3
获得 x86-64 的预期内循环AVX2。 (Godbolt)
.L14:
movdqu xmm2, XMMWORD PTR [rax]
add rax, 16
pmaxub xmm0, xmm2
cmp rax, rdx
jne .L14
pmaxsb
requires SSE4.1。 (SSE2 与 MMX 一样是高度非正交的,一些操作仅适用于某些大小和符号组合,针对特定应用程序,如音频 DSP 和图形像素。SSE4.1 填补了许多空白。)
如果启用它,GCC 和 clang 将使用它。
仅使用 -O3
和基线 x86-64 -march
默认值(和 -mtune=generic
),GCC 使用 pcmpgtb
自动矢量化(这是一个带符号的比较)然后使用 pand
/pandn
/por
和必要的额外 movdqa
复制进行手动混合。 pcmpgtb
提示您编写的代码需要签名比较,而不是未签名。 Clang 做同样的事情。
.L5:
movdqu xmm1, XMMWORD PTR [rax]
add rax, 16
movdqa xmm2, xmm1
pcmpgtb xmm2, xmm0
pand xmm1, xmm2
pandn xmm2, xmm0
movdqa xmm0, xmm2
por xmm0, xmm1
cmp rax, rdx
jne .L5
GCC 可以通过 pmaxub
的范围移位输入自动矢量化为无符号,然后通过 adding/subtracting 128.(即 pxor
和 _mm_set1_epi8(0x80)
)。因此,对于这种情况,这是一个很大的优化失误,它本来可以将关键路径延迟降低到 1 个周期,只是 pmaxub
。
当然,如果您实际启用 SSE4.1,您会得到 pmaxsb
。或者 AVX2 vpmaxsb
.
您 可以 使用 -msse4.1
或 -mavx2
但通常您希望启用更新的 CPU 也有的其他扩展,并设置调整设置。特别是对于 AVX2,您不想针对 Sandybridge 和更旧的 CPU 进行调整,因为 SnB 甚至 没有 AVX2。您不希望拆分未对齐的负载和类似的东西。此外,AVX2 CPUs 通常也有 BMI2、popcnt 和其他好东西。
使用-march=haswell
或-march=znver1
(禅)。或者对于本地使用,-march=native
来优化您的 CPU。 (如果你有 Skylake,它与使用 -march=skylake
相同,除非它可能检测到你的特定 L3 缓存大小或其他东西。)