首页 文章

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

提问于
浏览
4

幸运的是 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

我希望在 eax 中有一个所有非零寄存器的位图(显然我可以在多个寄存器中组合多个位图) .

因此,每个测试的延迟为3 1 = 4个周期 .
其中一些可以在 eaxecx 等之间交替并行运行 .
但它仍然很慢 .
有更快的方法吗?

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

1 回答

  • 6

    实际上,现有方法不是“非常慢”,而是合理的 .

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

    在英特尔硬件上可能更快一点的方法是使用连续的 PCMPEQ 指令,测试几个向量,然后将结果折叠在一起(例如,如果你使用PCMPEQQ,你实际上有4个四字结果,需要折叠它们进入1) . 您可以在 PCMPEQ 之前或之后折叠,但这将有助于更多地了解您希望结果如何/在哪里提出更好的结果 . 这是一个未经测试的8个寄存器草图,其中 xmm1-8 假设为零,而 xmm14xmm14 掩码,用于选择最后一条指令中使用的备用字节 .

    # 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个uop,所以每个寄存器大约有2个uop . 总延迟是合理的,因为它主要是"reduce"类型的并行树 . 一个限制因素是6 vpblendw 操作,这些操作都仅限于现代英特尔的端口5 . 最好用 VPBLENDD 替换其中的4个,这是_2904699_的任何一个"blessed"混合 . 这应该是直截了当的 .

    所有操作都简单快捷 . 最终的 and al, ah 是一个部分寄存器写入,但是如果你 mov 之后进入 eax 也许没有惩罚 . 如果这是一个问题,你也可以通过几种不同的方式完成最后一行......

    这种方法也可以自然地扩展到 ymm 寄存器,最后在 eax 中折叠略有不同 .

    EDIT

    稍微快一点的结尾使用打包移位来避免两个昂贵的指令:

    ;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 的依赖性 .


    事实上,似乎只有在Skylake上 PTEST 有三个周期的延迟,之前它似乎是2.我也不确定你为 rcl eax, 1 列出的1个周期延迟:根据Agner,它似乎是3现代英特尔的uops和2个周期延迟/接收器吞吐量 .

相关问题