测试xmm/ymm寄存器是否为零的更快方法?



幸运的是,PTEST

不会影响进位标志,而只是设置(相当笨拙的)ZF。

我想出了以下序列来测试大量值,但我对糟糕的运行时间不满意。

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 个周期。
其中一些可以通过在eaxecx等之间交替并行运行,
但它仍然很慢。
有没有更快的方法可以做到这一点?

我需要连续测试 8 个 xmm/ymm 寄存器。 每个寄存器在 1 字节位图中 1 位。

实际上,您现有的方法不是"相当慢",而是合理的。

当然,每个单独的测试都有 4 个周期1的延迟,但如果您希望结果出现在通用寄存器中,您通常会为该移动支付 3 个周期的延迟(例如,movmskb也有 3 个周期的延迟)。在任何情况下,您都希望测试 8 个寄存器,并且您不能简单地添加延迟,因为每个寄存器大多是独立的,因此 uop 计数和端口使用最终可能比测试单个寄存器的延迟更重要,因为大多数延迟将与其他工作重叠。

在英特尔硬件上可能更快一点的方法是使用连续的PCMPEQ指令,测试多个向量,然后将结果折叠在一起(例如,如果您使用 PCMPEQQ,您实际上有 4 个四字结果,需要将它们折叠成 1)。您可以在PCMPEQ之前或之后折叠,但更多地了解您希望如何/在哪里获得结果以提出更好的结果会有所帮助。这是 8 个寄存器的未经测试的草图,xmm1-8xmm0假定为零,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 位移动到带有movmskbeax中,最后将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 个周期vpblendvbor al,ah的部分写入损失来节省 2 个周期,如果不需要立即使用该指令的结果,它还修复了对慢速vpmovmskb的依赖。


1实际上,似乎只有在 Skylake 上,PTEST的延迟为三个周期,在此之前它似乎是 2。我也不确定您为rcl eax, 1列出的 1 个周期延迟:根据 Agner 的说法,在现代英特尔上似乎是 3 uops 和 2 个周期延迟/往复吞吐量。

相关内容

最新更新