最有效的方法是利用AVX隐式清零到VLMAX(由XCR0的当前值确定的最大向量寄存器宽度):
vpxor xmm6, xmm6, xmm6
vpxor xmm7, xmm7, xmm7
vpxor xmm8, xmm0, xmm0 # still a 2-byte VEX prefix as long as the source regs are in the low 8
vpxor xmm9, xmm0, xmm0
这些只有4字节指令(2字节VEX前缀),而不是6字节(4字节EVEX前缀)。请注意在低8位中使用源寄存器,即使目的地是xmm8-xmm15,也可以使用2字节VEX。(当第二个源寄存器为x / ymm8-15时需要3字节VEX前缀)。是的,只要两个源操作数是相同的寄存器(我测试了Skylake上它不使用执行单元),它仍然被认为是一个清零惯用语。
除了代码大小影响,性能在Skylake-AVX512和KNL上与vpxord/q zmm和vxorps zmm完全相同。 (较小的代码几乎总是更好的。)但请注意,KNL具有非常弱的前端,其中最大解码吞吐量仅能勉强饱和矢量执行单元,并且通常是
Agner Fog's microarch guide所述的瓶颈。 (它没有uop缓存或循环缓冲区,并且最大吞吐量为每个时钟周期2条指令。此外,平均提取吞吐量限制为每个周期16B。)
此外,在假设的未来的AMD(或者可能是英特尔)CPU中,将AVX512指令解码为两个256b uops(或四个128b uops)会更加高效。当前的AMD CPU(包括Ryzen)直到解码vpxor ymm0, ymm0, ymm0
为2个uops后才检测清零习语,所以这是一个真实的问题。旧版本的编译器出现了错误(gcc bug 80636, clang bug 32862),但这些优化错误已在当前版本中得到修复(GCC8、clang6.0、MSVC从一开始就修复了(?). ICC仍然不够优秀。)
将zmm16-31清零确实需要使用EVEX编码的指令;vpxord
或vpxorq
都是同样好的选择。EVEX vxorps
由于某些原因需要AVX512DQ(在KNL上不可用),但EVEX vpxord/q
是基线AVX512F。
vpxor xmm14, xmm0, xmm0
vpxor xmm15, xmm0, xmm0
vpxord zmm16, zmm16, zmm16
vpxord zmm17, zmm17, zmm17
EVEX前缀是固定宽度的,因此使用zmm0没有任何好处。
如果目标支持AVX512VL(Skylake-AVX512但不支持KNL),则仍然可以使用“vpxord xmm31,…”以获得更好的性能,因为未来的CPU会将512b指令解码成多个uop。
如果您的目标具有AVX512DQ(Skylake-AVX512但不支持KNL),在创建FP数学指令的输入时使用vxorps可能是一个好主意,或者在任何其他情况下使用vpxord。对Skylake没有影响,但某些未来的CPU可能会关注这一点。如果总是使用vpxord更容易,请不要担心这个问题。
相关:在zmm寄存器中生成全1的最佳方法似乎是
vpternlogd zmm0,zmm0,zmm0, 0xff
。(使用全1的查找表,逻辑表中的每个条目都为1)。
vpcmpeqd same,same
无法工作,因为AVX512版本会将比较结果存储到一个掩码寄存器而不是向量寄存器中。
这种特殊情况的
vpternlogd/q
在KNL或Skylake-AVX512上没有独立的特殊情况,因此尽量选择一个未使用的寄存器。在SKL-avx512上相当快,根据我的测试,每个时钟周期可以处理2次。(如果需要多个全1的寄存器,请使用vpternlogd并复制结果,特别是如果您的代码只在Skylake上运行而不仅仅是在KNL上运行)。
我选择了32位元素大小(使用
vpxord
而不是
vpxorq
),因为32位元素大小被广泛使用,如果一个元素大小较慢,通常不是32位元素。例如,在Silvermont上,
pcmpeqq xmm0,xmm0
比
pcmpeqd xmm0,xmm0
慢得多。在AVX512之前,
pcmpeqw
是生成全1向量的另一种方法,但gcc选择
pcmpeqd
。我非常确定对于xor-zeroing来说这永远不会有影响,特别是没有掩码寄存器,但如果你正在寻找选择
vpxord
或
vpxorq
的理由,除非有人在任何AVX512硬件上发现真正的性能差异,否则这是一个很好的理由。
有趣的是gcc选择vpxord
,但选择vmovdqa64
而不是vmovdqa32
。
XOR-zeroing doesn't use an execution port at all on Intel SnB-family CPUs, including Skylake-AVX512. (TODO: incorporate some of this into that answer, and make some other updates to it...)
但是在KNL上,我相信xor-zeroing需要一个执行端口。两个向量执行单元通常可以跟上前端,因此在发出/重命名阶段处理xor-zeroing在大多数情况下不会产生性能差异。vmovdqa64
/vmovaps
根据Agner Fog的测试需要一个端口(更重要的是具有非零延迟),因此我们知道它不会在发出/重命名阶段处理这些操作。(它可能像Sandybridge一样消除xor-zeroing但不消除mov操作。但我怀疑这样做没有什么好处。)
如 Cody 指出,Agner Fog 的表格表明 KNL 在 FP0/1 上同时运行
vxorps/d
和
vpxord/q
,前提是它们需要一个端口,并且具有相同的吞吐量和延迟。我认为这仅适用于 xmm/ymm
vxorps/d
,除非英特尔的文档存在错误并且 EVEX
vxorps zmm
可以在 KNL 上运行。
另外,在 Skylake 及更高版本中,非零化的
vpxor
和
vxorps
运行在相同的端口上。矢量整数布尔运算在 Intel Nehalem 至 Broadwell,即不支持 AVX512 的 CPU 上才会占用更多的端口。(即使在 Nehalem 上进行零化,它也实际上需要一个 ALU 端口,尽管它被认为与旧值无关)。
Skylake上的旁路延迟取决于它选择哪个端口,而不是你使用了哪个指令。例如,如果将
vandps
调度到p0或p1,那么
vaddps
读取
vandps
的结果就会增加一个周期的延迟。请参阅英特尔的优化手册以获取表格。更糟糕的是,这种额外的延迟会永久存在,即使结果在寄存器中等待数百个周期才被读取。它会影响来自另一个输入到输出的依赖链,因此在这种情况下仍然很重要。(TODO:整理一下我的实验结果并发布到某个地方。)
vpxor xmm, xmm, xmm
会清除目标寄存器的上半部分。参考:Intel® 64和IA-32体系结构软件开发人员手册*2.3.10.1向量长度转换和编程注意事项[...]程序员应该记住,使用VEX.128和VEX.256前缀编码的指令将清除任何未来对向量寄存器的扩展[...] - EOF