实际上,您现有的方法不是“相当慢”而是合理的。
当然,每个单独的测试都有 4 个周期的延迟1,但是如果您希望将结果保存在通用寄存器中,您通常需要支付 3 个周期无论如何,该移动的延迟(例如,movmskb 的延迟也为 3)。在任何情况下,您都想测试 8 个寄存器,并且您不能简单地添加延迟,因为每个寄存器大部分都是独立的,因此 uop 计数和端口使用最终可能比测试单个寄存器的延迟更重要的延迟将与其他工作重叠。
在 Intel 硬件上可能更快一点的方法是使用连续的 PCMPEQ 指令来测试多个向量,然后将结果折叠在一起(例如,如果您使用 PCMPEQQ,您实际上有 4 个四字结果并且需要将它们折叠成 1)。您可以在PCMPEQ 之前或之后弃牌,但这将有助于更多地了解您希望如何/在何处获得更好的结果。这是 8 个寄存器的未经测试的草图,xmm1-8,xmm0 假定为零,xmm14 是 pblendvb 掩码,用于选择最后一条指令中使用的备用字节。
# test the 2 qwords in each vector against zero
vpcmpeqq xmm11, xmm1, xmm0
vpcmpeqq xmm12, xmm3, xmm0
vpcmpeqq xmm13, xmm5, xmm0
vpcmpeqq xmm14, xmm7, xmm0
# blend the results down into xmm10 word origin
vpblendw xmm10, xmm11, xmm12, 0xAA # 3131 3131
vpblendw xmm13, xmm13, xmm14, 0xAA # 7575 7575
vpblendw xmm10, xmm10, xmm13, 0xCC # 7531 7531
# test the 2 qwords in each vector against zero
vpcmpeqq xmm11, xmm2, xmm0
vpcmpeqq xmm12, xmm4, xmm0
vpcmpeqq xmm13, xmm6, xmm0
vpcmpeqq xmm14, xmm8, xmm0
# blend the results down into xmm11 word origin
vpblendw xmm11, xmm11, xmm12, 0xAA # 4242 4242
vpblendw xmm13, xmm13, xmm14, 0xAA # 8686 8686
vpblendw xmm11, xmm11, xmm13, 0xCC # 8642 8642
# blend xmm10 and xmm11 together int xmm100, byte-wise
# origin bytes
# xmm10 77553311 77553311
# xmm11 88664422 88664422
# res 87654321 87654321
vpblendvb xmm10, xmm10, xmm11, xmm15
# move the mask bits into eax
vpmovmskb eax, xmm10
and al, ah
直觉是您测试每个xmm 中的每个QWORD 是否为零,为8 个寄存器提供16 个结果,然后将结果混合到xmm10 中,最后每个字节有一个结果,按顺序(所有高 QWORD 结果在所有低 QWORD 结果之前)。然后将这 16 字节掩码作为 16 位移动到 eax 和 movmskb 中,最后将 eax 中每个寄存器的高位和低位 QWORD 组合起来。
在我看来,总共 16 个微指令,8 个寄存器,所以每个寄存器大约 2 个微指令。总延迟是合理的,因为它主要是“减少”类型的并行树。一个限制因素是 6 个vpblendw 操作,它们都只进入现代英特尔的端口 5。最好用VPBLENDD 替换其中的4 个,这是p015 中任何一个的“祝福”混合。这应该很简单。
所有操作都简单快速。最终的and al, ah 是部分寄存器写入,但如果您将mov 写入eax 之后可能不会受到任何惩罚。如果这是一个问题,您还可以通过几种不同的方式来执行最后一行...
这种方法还可以自然地扩展到ymm 寄存器,最后在eax 中的折叠略有不同。
编辑
稍快的结束使用打包移位来避免两条昂贵的指令:
;combine bytes of xmm10 and xmm11 together into xmm10, byte wise
; xmm10 77553311 77553311
; xmm11 88664422 88664422 before shift
; xmm10 07050301 07050301
; xmm11 80604020 80604020 after shift
;result 87654321 87654321 combined
vpsrlw xmm10,xmm10,8
vpsllw xmm11,xmm11,8
vpor xmm10,xmm10,xmm11
;combine the low and high dqword to make sure both are zero.
vpsrldq xmm12,xmm10,64
vpand xmm10,xmm12
vpmovmskb eax,xmm10
这通过避免 2 个周期 vpblendvb 和 or al,ah 的部分写入惩罚来节省 2 个周期,如果不需要立即使用该指令的结果,它还修复了对慢速 vpmovmskb 的依赖.
1实际上似乎只有在 Skylake 上 PTEST 的延迟为 3 个周期,之前似乎为 2。我也不确定您的 1 个周期延迟列出rcl eax, 1:根据Agner 的说法,现代英特尔似乎是3 微指令和2 周期延迟/接收吞吐量。