我想要实现的是基于字节中的每个位,设置为ymm寄存器(或存储位置)中每个双字中的所有位
e.g。
al = 0110 0001
ymm0 = 0x00000000 FFFFFFFF FFFFFFFF 00000000 00000000 00000000 00000000 FFFFFFFF
即。与vmovmskps eax, ymm0
/ _mm256_movemask_ps
相反,将位图转换为矢量蒙版。
我认为有一些sse / avx指令可以相对简单地执行此操作,但我还没有能够解决这个问题。优选沙桥兼容,因此没有avx2。
答案 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)。 mask
为set1_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。所以复制和随机播放vpermilps
或vshufps
都可以。
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个。
但是没有比较浮点数危险或不良做法?
当其中一个比较输入是计算的舍入结果(例如vaddps
或vmulps
的输出)时,精确相等的比较可能会产生意外结果。布鲁斯道森关于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。
我无法测试它,但它应该是你想要的。