如何在没有AVX2的情况下使用字节中的位来设置ymm寄存器中的dword? (反向vmovmskps)

时间:2018-02-15 16:00:18

标签: assembly x86-64 sse avx

我想要实现的是基于字节中的每个位,设置为ymm寄存器(或存储位置)中每个双字中的所有位

e.g。

al = 0110 0001

ymm0 = 0x00000000 FFFFFFFF FFFFFFFF 00000000 00000000 00000000 00000000 FFFFFFFF

即。与vmovmskps eax, ymm0 / _mm256_movemask_ps相反,将位图转换为矢量蒙版。

我认为有一些sse / avx指令可以相对简单地执行此操作,但我还没有能够解决这个问题。优选沙桥兼容,因此没有avx2。

2 个答案:

答案 0 :(得分:6)

is there an inverse instruction to the movemask instruction in intel avx2?中所述,您可以将位图拆分为两个4位块,以便与LUT一起使用。这表现相当不错:vinsertf128在Sandybridge上每时钟吞吐量为1,在Haswell / Skylake上每0.5c有一个。

使用AVX1的ALU解决方案可以对高/低矢量一半执行相同的工作两次(广播位图,屏蔽它,vpcmpeqd xmm),然后vinsertf128,但这有点糟糕。

您可以考虑使用vpbroadcastd ymm0, mem / vpand ymm0, mask / vpcmpeqd dst, ymm0, mask将AVX2版本与仅AVX1版本分开,因为非常高效,特别是如果你从内存中加载位图,你可以读取位图的整个双字。 (dword或qword的广播负载不需要ALU shuffle)。 maskset1_epi32(1<<7, 1<<6, 1<<5< ..., 1<<0),您可以使用vpmovzxbd ymm, qword [constant]加载,因此8个元素只需要8个字节的数据内存。

如果我们有创意,我们可以使用AVX1 FP指令来做同样的事情。 AVX1有dword广播(vbroadcastss ymm0, mem)和布尔值(vandps)。这将生成有效single-precision floats的位模式,因此我们可以使用vcmpeqps,但如果我们将位图位留在元素的底部,则它们都是非正规的。在Sandybridge上实际上可能没问题:比较非正规数可能没有任何惩罚。但是如果你的代码运行DAZ(denormals-are-zero)会破坏它,所以我们应该避免这种情况。

我们可以vpor在屏蔽之前或之后设置指数,或者我们可以将位图向上移动到IEEE浮点格式的8位指数字段。如果您的位图在整数寄存器中启动,那么移位它会很好,因为shl eax, 23之前的movd便宜。  但如果它从内存开始,那就意味着放弃使用廉价的vbroadcastss负载。或者您可以广播加载到xmm,vpslld xmm0, xmm0, 23 / vinsertf128 ymm0, xmm0, 1。但这仍然比vbroadcastss / vorps / vandps / vcmpeqps

更糟糕

所以:

# untested
# pointer to bitmap in rdi
inverse_movemask:
    vbroadcastss  ymm0, [rdi]

    vorps         ymm0, ymm0, [set_exponent]   ; or hoist this constant out with a broadcast-load

    vmovaps       ymm7, [bit_select]          ; hoist this out of any loop, too
    vandps        ymm0, ymm0, ymm7
    ; ymm0 exponent = 2^0, mantissa = 0 or 1<<i where i = element number
    vcmpeqps      ymm0, ymm0, ymm7
    ret

section .rodata
ALIGN 32
    bit_select: dd 0x3f800000 + 1<<7, 0x3f800000 + 1<<6
                dd 0x3f800000 + 1<<5, 0x3f800000 + 1<<4
                dd 0x3f800000 + 1<<3, 0x3f800000 + 1<<2
                dd 0x3f800000 + 1<<1, 0x3f800000 + 1<<0

    set_exponent: times 8 dd 0x3f800000    ; 1.0f
    ;  broadcast-load this instead of duplicating it in memory if you're hoisting it.

而不是广播加载set_exponent,您可以改为bit_select:只要设置了0x3f800000位,如果元素0也设置了位,则无关紧要3或者什么,只是不是0。所以复制和随机播放vpermilpsvshufps都可以。

https://www.h-schmidt.net/FloatConverter/IEEE754.html是有用的IEEE754 FP值&lt; - &gt;十六进制位模式转换器,以防您想要检查某些FP位模式代表什么值。

vcmpeqps在所有Intel CPU上具有与vaddps相同的延迟和吞吐量。 (这不是不是巧合;它们在同一个执行单元上运行)。这意味着SnB-Broadwell的3个周期延迟和Skylake的4个周期延迟。但vpcmpeqd只有1c延迟。

因此,此方法具有良好的吞吐量(仅比AVX2整数高1个uop,其中vorps不需要),但延迟3个周期,或Skylake的4个。

但是没有比较浮点数危险或不良做法?

当其中一个比较输入是计算的舍入结果(例如vaddpsvmulps的输出)时,精确相等的比较可能会产生意外结果。布鲁斯道森关于FP数学的博客系列,特别是x86,非常出色,特别是Comparing Floating Point Numbers, 2012 Edition 。但在这种情况下,我们控制FP位模式,并且没有舍入。

具有相同位模式的非NaN FP值将始终相等。

具有不同位模式的FP值将始终比较为不相等,除了-0.0+0.0(仅在符号位上有所不同),以及DAZ模式下的非规范化值。后者是我们使用vpor的原因;如果您知道DAZ被禁用且您的FP硬件不需要辅助来比较非正规,您可以跳过它。 (IIRC,Sandybridge没有,甚至可以在没有辅助的情况下添加/删除非正规。当英特尔硬件上需要微代码辅助时,通常在从正常输入产生非正规结果时,但是比较并不是; t产生FP结果。)

答案 1 :(得分:5)

前言:我知道这并不能满足问题的(整体)要求,所以这个答案是不可接受的。 我只是发布它以供将来参考。< /强>

有一个名为VPMOVM2B的新AVX512(VL | BW)指令可以在一个指令中执行您想要的操作:

VPMOVM2B ymm1, k1
  

根据k1中相应位的值,将YMM1中的每个字节设置为全1或全0。

我无法测试它,但它应该是你想要的。