如何在XMM寄存器中对值进行逐位否定?据我所知,没有这样的指示。唯一带有否定的指令是pandn
,但是要用它来简单地否定一个XMM寄存器中的值,我必须让另一个填充了1
的XMM寄存器填充。
是否有另一种方法来否定XMM寄存器中的位?或者是否有一种聪明的方法可以在不访问内存的情况下用1
填充XMM寄存器?
答案 0 :(得分:5)
要加载一个包含全1的寄存器,请使用
pcmpeqd xmm0, xmm0
之后,您只需从xmmX
中扣除xmm0
即可获得~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
答案 1 :(得分:5)
将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复制和微融合的load + pxor是3个未融合域的总和。2)
或者在没有AVX的情况下,如果要破坏全常量而不是要反转的数据,则pxor
会再次获胜:
movdqa xmm2, xmm1 ; copy the all-ones constant. Off the critical path for latency
pxor xmm2, xmm0
与movdqa
/ movdqa xmm2, xmm0
相比,您可以从关键路径上撤消pandn xmm2,xmm1
。 (只有IvyBridge +和Bulldozer-family / Ryzen对向量寄存器使用零延迟movdqa
。)或者如果您每次在目标寄存器中使用pcmpeqd
重新实现全一(也许是由于寄存器压力)或因为您没有循环执行),那是您想要pxor
而不是pandn
的另一种情况。
使用pcmpeqb/w/d
生成全常量是特殊情况,它不会对旧值有错误的依赖性(在Silvermont上除外),但是仍然需要一个执行单元(与异或归零不同)在桑迪布里奇家庭)。尽管如此,它还是便宜的,并且是编译器用于_mm_set1_epi32(-1)
的功能。
在IvyBridge和更高版本以及Bulldozer系列和Ryzen上,每次需要它时都重新创建该常量,而不是从另一个寄存器中复制它会变得稍微。 mov
消除XMM副本可以避免占用向量执行单元/端口,以防向量ALU执行端口成为您的瓶颈。
但是在Intel P6-family(Core2 / Nehalem)上要好一些:在问题组中读取过多的“冷”寄存器时,寄存器读取停顿可能是个问题。 (请参阅Agner Fog的microarch pdf https://agner.org/optimize/)。 P6系列已过时,但仍在某些旧机器中使用。如果您有在具有AVX的CPU上运行的AVX版本,则可能需要在代码的非AVX版本中进行调整。 (但是Haswell / Skylake的“ pentium” /“ celeron”还是一回事,它们没有AVX,所以没有AVX并不意味着旧的CPU。)