XMM 寄存器的按位求反
Bitwise negation of XMM register
如何对 XMM 寄存器中的值进行按位求反?据我所知没有这样的说明。唯一带有否定的指令是 pandn
,但要使用它来简单地否定一个 XMM 寄存器中的值,我必须让另一个 XMM 寄存器填充 1
。
还有其他方法可以对 XMM 寄存器中的位求反吗?或者有没有一种聪明的方法可以在不访问内存的情况下用 1
填充 XMM 寄存器?
要加载全 1 的寄存器,请使用
pcmpeqd xmm0, xmm0
之后,您可以简单地从 xmm0
中减去 xmmX
得到 ~xmmX
,或者使用 pandn
您还可以轻松地将其他常量加载到 xmm 寄存器
pcmpeqd xmm0, xmm0
psrld xmm0, 30 ; 3 (32-bit)
pcmpeqd xmm0, xmm0 ; -1
pcmpeqw xmm0, xmm0 ; 1.5f
pslld xmm0, 24
psrld xmm0, 2
pcmpeqw xmm0, xmm0 ; -2.0f
pslld xmm0, 30
阅读 Agner Fog 的优化指南,13.4 Generating constants - Making constants for integer vectors in XMM registers
将 pxor
与全一寄存器一起使用。
pandn
也可用,但优势为零。没有任何情况下 pandn
和全一常量可以让你做任何用 pxor
.
做不到的事情
psubd
也可用(2 的补码标识),但比 pandn
更差,因为它在某些 CPU 上的吞吐量较低(执行端口较少)。
pcmpedq xmm1, xmm1 ; create the all-ones. No false dependency.
pxor xmm0, xmm1 ; flip all the bits in XMM0. Doesn't destroy XMM1
;pandn xmm0, xmm1 ; equivalent but no advantage. (~xmm0) & xmm1
PXOR 很好,因为它是可交换的。使用 AVX,您可以使用一个微融合 uop 加载和不加载:
vpxor xmm0, xmm1, [rdi]
你不能用 VPANDN 做到这一点,因为可以是内存或寄存器的操作数是非反转操作数。 (不过,如果没有 AVX,只需 movdqa
或 dqu
加载,然后 pxor
加载结果。reg-copy 和微融合加载+pxor 总共有 3 个未融合域 uops vs . 2)
或者没有 AVX,如果你想销毁全一常数而不是你正在反转的数据,pxor
再次获胜:
movdqa xmm2, xmm1 ; copy the all-ones constant. Off the critical path for latency
pxor xmm2, xmm0
与 movdqa xmm2, xmm0
/ pandn xmm2,xmm1
相比,您可以从关键路径上取 movdqa
。 (只有 IvyBridge+ 和 Bulldozer-family/Ryzen 具有矢量寄存器的零延迟 movdqa
。)或者如果您每次都在目标寄存器中使用 pcmpeqd
重新实现全一(可能是因为寄存器压力或者因为你没有在循环中这样做),那将是另一种你想要 pxor
而不是 pandn
.
的情况
用 pcmpeqb/w/d
生成一个全一常量是特殊情况,不会对旧值有错误的依赖(除了在 Silvermont 上它有),但仍然需要一个执行单元(不像 xor - 在 Sandybridge 系列上归零)。尽管如此,它还是很便宜,而且它是编译器用于 _mm_set1_epi32(-1)
.
的东西
每次需要时重新创建常量而不是从另一个寄存器复制,稍微在 IvyBridge 和更高版本以及 Bulldozer 系列和 Ryzen 上更糟。 mov
-消除 XMM 副本可避免占用向量执行单元/端口,以防向量 ALU 执行端口成为瓶颈。
但它在 Intel P6 系列 (Core2/Nehalem) 上稍微好一些:当在一个问题组中读取太多 "cold" 寄存器时,寄存器读取停顿可能是一个问题。 (参见 Agner Fog 的 microarch pdf https://agner.org/optimize/)。 P6-family 已经过时,但仍在一些旧机器中使用。如果您的 AVX 版本运行在 CPUs 上且带有 AVX,则您可能希望在代码的非 AVX 版本中对其进行调整。 (但是 Haswell/Skylake "pentium" / "celeron" 仍然是一个东西,他们没有 AVX,所以没有 AVX 并不意味着旧的 CPU。)
如何对 XMM 寄存器中的值进行按位求反?据我所知没有这样的说明。唯一带有否定的指令是 pandn
,但要使用它来简单地否定一个 XMM 寄存器中的值,我必须让另一个 XMM 寄存器填充 1
。
还有其他方法可以对 XMM 寄存器中的位求反吗?或者有没有一种聪明的方法可以在不访问内存的情况下用 1
填充 XMM 寄存器?
要加载全 1 的寄存器,请使用
pcmpeqd xmm0, xmm0
之后,您可以简单地从 xmm0
中减去 xmmX
得到 ~xmmX
,或者使用 pandn
您还可以轻松地将其他常量加载到 xmm 寄存器
pcmpeqd xmm0, xmm0
psrld xmm0, 30 ; 3 (32-bit)
pcmpeqd xmm0, xmm0 ; -1
pcmpeqw xmm0, xmm0 ; 1.5f
pslld xmm0, 24
psrld xmm0, 2
pcmpeqw xmm0, xmm0 ; -2.0f
pslld xmm0, 30
阅读 Agner Fog 的优化指南,13.4 Generating constants - Making constants for integer vectors in XMM registers
将 pxor
与全一寄存器一起使用。
pandn
也可用,但优势为零。没有任何情况下 pandn
和全一常量可以让你做任何用 pxor
.
psubd
也可用(2 的补码标识),但比 pandn
更差,因为它在某些 CPU 上的吞吐量较低(执行端口较少)。
pcmpedq xmm1, xmm1 ; create the all-ones. No false dependency.
pxor xmm0, xmm1 ; flip all the bits in XMM0. Doesn't destroy XMM1
;pandn xmm0, xmm1 ; equivalent but no advantage. (~xmm0) & xmm1
PXOR 很好,因为它是可交换的。使用 AVX,您可以使用一个微融合 uop 加载和不加载:
vpxor xmm0, xmm1, [rdi]
你不能用 VPANDN 做到这一点,因为可以是内存或寄存器的操作数是非反转操作数。 (不过,如果没有 AVX,只需 movdqa
或 dqu
加载,然后 pxor
加载结果。reg-copy 和微融合加载+pxor 总共有 3 个未融合域 uops vs . 2)
或者没有 AVX,如果你想销毁全一常数而不是你正在反转的数据,pxor
再次获胜:
movdqa xmm2, xmm1 ; copy the all-ones constant. Off the critical path for latency
pxor xmm2, xmm0
与 movdqa xmm2, xmm0
/ pandn xmm2,xmm1
相比,您可以从关键路径上取 movdqa
。 (只有 IvyBridge+ 和 Bulldozer-family/Ryzen 具有矢量寄存器的零延迟 movdqa
。)或者如果您每次都在目标寄存器中使用 pcmpeqd
重新实现全一(可能是因为寄存器压力或者因为你没有在循环中这样做),那将是另一种你想要 pxor
而不是 pandn
.
用 pcmpeqb/w/d
生成一个全一常量是特殊情况,不会对旧值有错误的依赖(除了在 Silvermont 上它有),但仍然需要一个执行单元(不像 xor - 在 Sandybridge 系列上归零)。尽管如此,它还是很便宜,而且它是编译器用于 _mm_set1_epi32(-1)
.
每次需要时重新创建常量而不是从另一个寄存器复制,稍微在 IvyBridge 和更高版本以及 Bulldozer 系列和 Ryzen 上更糟。 mov
-消除 XMM 副本可避免占用向量执行单元/端口,以防向量 ALU 执行端口成为瓶颈。
但它在 Intel P6 系列 (Core2/Nehalem) 上稍微好一些:当在一个问题组中读取太多 "cold" 寄存器时,寄存器读取停顿可能是一个问题。 (参见 Agner Fog 的 microarch pdf https://agner.org/optimize/)。 P6-family 已经过时,但仍在一些旧机器中使用。如果您的 AVX 版本运行在 CPUs 上且带有 AVX,则您可能希望在代码的非 AVX 版本中对其进行调整。 (但是 Haswell/Skylake "pentium" / "celeron" 仍然是一个东西,他们没有 AVX,所以没有 AVX 并不意味着旧的 CPU。)