X86: 如何将 xmm0 的下半部分设置为 0,而不影响上半部分?
X86: How to set lower half of xmm0 to 0, without affecting the upper half?
我使用 xmm0 有 128 位的系统。
我想将 [63...0] 设置为零,而不影响 [127...64]。
我使用:
MOV RAX, 0xFFFFFFFFFFFFFFFF
MOVQ xmm2, RAX
PSHUFD xmm2, xmm2, 0b00001111
PAND xmm1, xmm2
有没有更快的方法?
您可以通过
更有效地创建常量
pcmpeqd xmm2,xmm2 ; xmm2 = all-ones. Needs any ALU port
pslldq xmm2, 8 ; left shift by 8 bytes. Needs the shuffle port
PAND xmm1, xmm2
(另见 Agner Fog's optimization guide; he has a section on creating constants on the fly. Also )
或者如@RossRidge 所建议的那样,如果您经常需要它以在高速缓存中保持热,那么使用常量的内存源操作数可能是最有效的,但不能只是将其从循环中提升并保持在循环中一个寄存器。
或混入新的低8字节零.
pxor xmm2, xmm2 ; xmm2=0; very efficient on Intel CPUs; no back-end uop
movsd xmm1, xmm2 ; runs on port5 only on Intel CPUs, like shuffles.
(作为内存加载,movsd
零扩展。但是对于 reg-reg 移动它并且 movss
保留目标上半部分不变。)
混合的替代方法更有效,但需要的不仅仅是 SSE2:
- SSE4.1:
pblendw xmm1, xmm2, 0b00001111
- 一切都更差(或速度相同但代码大小更差)。在 Intel 的端口 5 上仍然只有 运行s。 Ryzen 运行s movsd xmm,xmm
在比 pblendw
更多的端口上。低功耗 Atom/Silvermont 运行s movsd 在比 pblendw 更多的端口上,但 Goldmont 和 KNL 对此和 movsd 有 2/clock 吞吐量。所以还是比不上movsd.
- SSE4.1
blendpd xmm1, xmm2, 0b01
(或blendps
)- 与 vpblendd 一样高效,但如果在整数指令之间使用,会产生旁路转发延迟。如果您在吞吐量方面遇到瓶颈,这可能没问题,特别是如果您必须避免后端压力。
- AVX2:
vpblendd xmm1, xmm1, xmm2, 0b0011
- 运行s 在任何 AVX2 CPU 上的任何 ALU 端口上 CPU。
一些 CPUs 也可能在整数指令之间有 movsd
的旁路延迟,但 Sandybridge 系列对随机播放非常宽容。
在某些 CPU 上与 movsd
一样有效,仅需要 SSE1:
movhlps xmm1, xmm2
- 将 xmm1 的低 qword 替换为 xmm2 的高 qword(也是零)。在 Ryzen 或 Silvermont 上效率较低。
类似地,shufpd
和 shufps
可以将 xmm1
的上半部分复制到置零寄存器的上半部分。 (如果您不想破坏原始 reg,则很有用)。但是您可以使用 movsd
轻松高效地做到这一点。
也可能:movlps xmm, [mem]
加载零,可能是您刚刚存储到堆栈中。它不允许注册源操作数,并且需要 Intel 上的 port5 uop(洗牌/不常见混合)。它可以微融合到一个融合域 uop 中,但它比使用内存源的 pand
更糟糕,因为它可以 运行 在更少的端口上。
我使用 xmm0 有 128 位的系统。 我想将 [63...0] 设置为零,而不影响 [127...64]。 我使用:
MOV RAX, 0xFFFFFFFFFFFFFFFF
MOVQ xmm2, RAX
PSHUFD xmm2, xmm2, 0b00001111
PAND xmm1, xmm2
有没有更快的方法?
您可以通过
更有效地创建常量pcmpeqd xmm2,xmm2 ; xmm2 = all-ones. Needs any ALU port
pslldq xmm2, 8 ; left shift by 8 bytes. Needs the shuffle port
PAND xmm1, xmm2
(另见 Agner Fog's optimization guide; he has a section on creating constants on the fly. Also
或者如@RossRidge 所建议的那样,如果您经常需要它以在高速缓存中保持热,那么使用常量的内存源操作数可能是最有效的,但不能只是将其从循环中提升并保持在循环中一个寄存器。
或混入新的低8字节零.
pxor xmm2, xmm2 ; xmm2=0; very efficient on Intel CPUs; no back-end uop
movsd xmm1, xmm2 ; runs on port5 only on Intel CPUs, like shuffles.
(作为内存加载,movsd
零扩展。但是对于 reg-reg 移动它并且 movss
保留目标上半部分不变。)
混合的替代方法更有效,但需要的不仅仅是 SSE2:
- SSE4.1:
pblendw xmm1, xmm2, 0b00001111
- 一切都更差(或速度相同但代码大小更差)。在 Intel 的端口 5 上仍然只有 运行s。 Ryzen 运行smovsd xmm,xmm
在比pblendw
更多的端口上。低功耗 Atom/Silvermont 运行s movsd 在比 pblendw 更多的端口上,但 Goldmont 和 KNL 对此和 movsd 有 2/clock 吞吐量。所以还是比不上movsd. - SSE4.1
blendpd xmm1, xmm2, 0b01
(或blendps
)- 与 vpblendd 一样高效,但如果在整数指令之间使用,会产生旁路转发延迟。如果您在吞吐量方面遇到瓶颈,这可能没问题,特别是如果您必须避免后端压力。 - AVX2:
vpblendd xmm1, xmm1, xmm2, 0b0011
- 运行s 在任何 AVX2 CPU 上的任何 ALU 端口上 CPU。
一些 CPUs 也可能在整数指令之间有 movsd
的旁路延迟,但 Sandybridge 系列对随机播放非常宽容。
在某些 CPU 上与 movsd
一样有效,仅需要 SSE1:
movhlps xmm1, xmm2
- 将 xmm1 的低 qword 替换为 xmm2 的高 qword(也是零)。在 Ryzen 或 Silvermont 上效率较低。
类似地,shufpd
和 shufps
可以将 xmm1
的上半部分复制到置零寄存器的上半部分。 (如果您不想破坏原始 reg,则很有用)。但是您可以使用 movsd
轻松高效地做到这一点。
也可能:movlps xmm, [mem]
加载零,可能是您刚刚存储到堆栈中。它不允许注册源操作数,并且需要 Intel 上的 port5 uop(洗牌/不常见混合)。它可以微融合到一个融合域 uop 中,但它比使用内存源的 pand
更糟糕,因为它可以 运行 在更少的端口上。