SSE2 是 x86-64 的基线,所以是pmaxub
可用的。
但是您的代码在 x86-64 System V ABI 和 Windows x64 中使用char
, 和char
= 。signed char
也许您来自 ARM where char
= unsigned char
?ISO C 标准将 char 实现的符号性定义为定义,因此依赖它来确保正确性(或在这种情况下为性能)是一个糟糕的主意。
如果您uint8_t
像普通人一样使用,您会从 GCC9.2 -O3
for x86-64 获得预期的内部循环,即使没有使用-march=skylake
或任何启用 AVX2。(神箭)
.L14:
movdqu xmm2, XMMWORD PTR [rax]
add rax, 16
pmaxub xmm0, xmm2
cmp rax, rdx
jne .L14
pmaxsb
需要 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 可以通过将输入范围移动到 unsigned for 来自动矢量化pmaxub
pxor
,然后通过添加/减去 128.(即使用)在循环外将范围移动回有符号_mm_set1_epi8(0x80)
。因此,对于这种情况,这是一个很大的错过优化,它可以将关键路径延迟降低到 1 个周期,只是pmaxub
.
但当然,如果你真的启用 SSE4.1,你会得到pmaxsb
. 或 AVX2 vpmaxsb
。
您可以使用-msse4.1
或者-mavx2
但通常您希望启用较新的 CPU 也具有的其他扩展,并设置调整设置。特别是对于 AVX2,您不想针对 Sandybridge 和较旧的 CPU 进行调整,因为 SnB 甚至没有AVX2。您不希望拆分未对齐的负载和类似的东西。此外,AVX2 CPU 通常也有 BMI2、popcnt 和其他好东西。
使用-march=haswell
或-march=znver1
(禅)。或供本地使用,-march=native
以针对您的 CPU 进行优化。-march=skylake
(如果您有 Skylake,则它与使用相同,除非它可能检测到您的特定 L3 缓存大小或其他东西。)