Joh*_*ica 4 optimization x86 assembly avx micro-optimization
幸运的是 PTEST 不影响进位标志,但只设置(相当笨拙)的ZF. 也影响CF和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
Run Code Online (Sandbox Code Playgroud)
我想要一个所有非零寄存器的eax位图(显然我可以在多个寄存器中组合多个位图).
因此,每个测试的延迟为3 + 1 = 4个周期.
一些本可以并行通过之间的交替运行eax,ecx等等
,但它仍然是相当缓慢.
有更快的方法吗?
我需要连续测试8个xmm/ymm寄存器.一个字节位图中每个寄存器1位.
实际上,现有方法不是"非常慢",而是合理的.
当然每个单独的测试都有4个周期1的延迟,但如果你想将结果放在通用寄存器中,你通常会为这个移动支付3个周期的延迟(例如,也有3个延迟).在任何情况下,您都想测试8个寄存器,而不是简单地添加延迟,因为每个寄存器大多是独立的,因此uop计数和端口使用可能最终会使测试单个寄存器的延迟变得更加重要延迟与其他工作重叠.movmskb
在英特尔硬件上可能更快一点的方法是使用连续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
Run Code Online (Sandbox Code Playgroud)
直觉是你QWORD在每个xmm中测试每个对零,为8个寄存器提供16个结果,然后你将结果混合在一起,xmm10最终每个字节有一个结果,按顺序排列(所有低QWORD结果都在之前) QWORD结果).然后将16字节掩码作为16位移入eax,movmskb最后将QWORD每个寄存器的高位和低位组合在一起eax.
对于8个寄存器来说,这对我来说总共有16个uop,所以每个寄存器大约有2个uop.总延迟是合理的,因为它主要是"减少"类型的并行树.一个限制因素是6个vpblendw操作,这些操作都只适用于现代英特尔的5号端口.最好将4个替换为VPBLENDD其中任何一个的"祝福"混合物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
Run Code Online (Sandbox Code Playgroud)
这通过避免2个周期vpblendvb和部分写入惩罚来节省2个周期,如果不需要立即使用该指令的结果or al,ah,它还修复了对慢速的依赖性vpmovmskb.
1实际上它似乎只在Skylake上PTEST有三个周期的延迟,之前它似乎是2.我也不确定你列出的1周期延迟rcl eax, 1:根据Agner,它似乎是3 uops现代英特尔的2个周期延迟/接收器吞吐量.