幸运的是,PTEST
我想出了以下序列来测试大量值,但我对糟糕的运行时间不满意。
Latency / rThoughput
setup:
xor eax,eax ; na
vpxor xmm0,xmm0 ; na ;mask to use for the nand operation of ptest
work:
vptest xmm4,xmm0 ; 3 1 ;is xmm4 alive?
adc eax,eax ; 1 1 ;move first bit into eax
vptest xmm5,xmm0 ; 3 1 ;is N alive?
adc eax,eax ; 1 1 ;move consecutive bits into eax
我想在eax
中拥有所有非零寄存器的位图(显然我可以在多个寄存器中组合多个位图)。
因此,每个测试的延迟为 3+1 = 4 个周期。
其中一些可以通过在eax
、ecx
等之间交替并行运行,
但它仍然很慢。
有没有更快的方法可以做到这一点?
我需要连续测试 8 个 xmm/ymm 寄存器。 每个寄存器在 1 字节位图中 1 位。
实际上,您现有的方法不是"相当慢",而是合理的。
当然,每个单独的测试都有 4 个周期1的延迟,但如果您希望结果出现在通用寄存器中,您通常会为该移动支付 3 个周期的延迟(例如,movmskb
也有 3 个周期的延迟)。在任何情况下,您都希望测试 8 个寄存器,并且您不能简单地添加延迟,因为每个寄存器大多是独立的,因此 uop 计数和端口使用最终可能比测试单个寄存器的延迟更重要,因为大多数延迟将与其他工作重叠。
在英特尔硬件上可能更快一点的方法是使用连续的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 位移动到带有movmskb
的eax
中,最后将eax
内每个寄存器的高低QWORD
位组合在一起。
在我看来,对于 8 个寄存器,总共 16 个 uops,所以每个寄存器大约 2 uops。总延迟是合理的,因为它在很大程度上是一个"减少"类型的并行树。一个限制因素是 6vpblendw
操作,它们都只转到现代英特尔上的端口 5。最好用VPBLENDD
替换其中的 4 个,这是p015
中的任何一个"祝福"混合物。这应该是直截了当的。
所有操作都简单快捷。最后and al, ah
是部分寄存器写入,但如果你在进入eax
之后mov
它,也许不会受到惩罚。如果这是一个问题,您也可以通过几种不同的方式执行最后一行......
这种方法也可以自然地扩展到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
的延迟为三个周期,在此之前它似乎是 2。我也不确定您为rcl eax, 1
列出的 1 个周期延迟:根据 Agner 的说法,在现代英特尔上似乎是 3 uops 和 2 个周期延迟/往复吞吐量。