是否有针对intel avx2中的movemask指令的反向指令?

orm*_*orm 8 x86 icc intrinsics avx avx2

movemask指令采用__m256i并返回int32,其中每个位(取决于输入向量元素类型的前4位,8位或所有32位)是相应向量元素的最高有效位.

我想做反过来:取一个32(其中只有4,8或32个最低有效位有意义),并获得__m256i,其中每个int8,int32或int64大小的块的最高有效位设置为原始位.

基本上,我想从压缩的位掩码转到可被其他AVX2指令(例如maskstore,maskload,mask_gather)用作掩码的位掩码.

我无法快速找到这样做的指令,所以我在这里问.如果没有一条具有该功能的指令,您是否可以想到一个聪明的黑客,只需很少的指令即可实现这一点?

我目前的方法是使用256元素查找表.我想在一个没有其他事情发生的循环中使用这个操作来加速它.注意,我对长多指令序列或实现此操作的小循环不太感兴趣.

Pet*_*des 10

AVX2或更早版本中没有单个指令.

如果您从内存加载位图,则将其直接加载到ALU策略的向量寄存器中应该可以正常工作.

如果您将位图作为计算结果,那么它将位于整数寄存器中,您可以轻松地将其用作LUT索引,因此如果您的目标是64位元素,那么这是一个不错的选择.否则可能仍然是ALU为32位元素或更小,而不是一个巨大的LUT或做多个块.


在从整数位掩码到矢量掩码的廉价转换之前,我们必须等待AVX-512的掩码寄存器.(有kmovw k1, r/m16,哪些编译器隐式生成int => __mmask16).有一个AVX512 insn来设置一个掩码的矢量(VPMOVM2D zmm1, k1,_mm512_movm_epi8/16/32/64其他版本用于不同的元素大小),但是你通常不需要它,因为过去使用掩码矢量的所有内容现在都使用掩码寄存器.也许你想要计算满足某些比较条件的元素?(您可以使用pcmpeqd/ psubd生成和累积0或-1元素的向量).但是popcnt掩码结果上的标量将是更好的选择.


对于64位元素,掩码只有4位,因此查找表是合理的.您可以通过加载来压缩LUT vpmovm2d.(k0..7).这使您的LUT大小为(1 << 4)= 16*4字节= 64B = 1个高速缓存行.不幸的VPMOVSXBQ ymm1, xmm2/m32是,使用内在函数作为窄负载是不方便的.

特别是如果你已经在整数寄存器(而不是内存)中使用了位图,那么_mm256_cvtepi8_epi64对于64位元素,内部循环中的LUT应该是非常好的.或者,如果指令吞吐量或随机吞吐量是瓶颈,请使用未压缩的LUT.这可以让你(或编译器)使用掩码向量作为其他东西的内存操作数,而不需要单独的指令来加载它.


对于32位元素的LUT:可能不是最佳的,但这是你如何做到的

对于32位元素,8位掩码为您提供256个可能的向量,每个长度为8个元素.256*8B = 2048字节,即使对于压缩版本(加载pmovsx),这也是一个非常大的缓存占用空间.

要解决此问题,您可以将LUT拆分为4位块.它需要大约3个整数指令才能将8位整数分成两个4位整数(vpmovsxbq).然后使用128b向量的未压缩LUT(对于32位元素大小),vpmovsxbd ymm, m64低半部分和mov/and/shr高半部分.你仍然可以压缩LUT,但我不推荐它,因为你需要vmovdqa/ vinserti128/ vmovd,这是2次shuffles(所以你可能是uop吞吐量的瓶颈).

或者2x vpinsrd+ vpmovsxbd在英特尔可能更糟糕.


ALU替代方案:适用于16/32/64位元素

当整个位图适合每个元素时,广播它,使用选择器掩码进行广播,并使用相同的常量对VPCMPEQ进行广播(它可以在循环中多次使用此寄存器时保留在寄存器中).

vpbroadcastd  ymm0,  dword [mask]
vpand         ymm0, ymm0,  [vec of 1<<0, 1<<1, 1<<2, 1<<3, ...]
vpcmpeqd      ymm0, ymm0,  [same constant]
      ; ymm0 =  (mask & bit) == bit
      ; where bit = 1<<element_number
Run Code Online (Sandbox Code Playgroud)

(掩码可能来自带有vmovd + vpbroadcastd的整数寄存器,但是广播负载

对于8位的元素,你需要vpmovsxbd xmm, [lut + rsi*4]vinserti128结果来获得相关位到每个字节.请参见如何执行_mm256_movemask_epi8(VPMOVMSKB)的反转?.但是对于16位和更宽的元素,元素的数量是<=元素宽度,因此广播负载是免费的.(16位广播负载确实需要一个微融合ALU shuffle uop,不像32和64位广播负载完全在负载端口处理.)

vpshufb甚至不花费任何ALU uops,它在加载端口完成.(vpbroadcastd并且vpbroadcastd/q正在加载+随机播放).即使你的掩码被打包在一起(每个字节一个32或64位元素),它可能仍然更有效率b而不是w.该vpbroadcastd检查不关心高字节播出后各元素的垃圾.唯一的担心是缓存行/页面拆分.


如果您只需要符号位,可变班次(Skylake便宜)

可变混合和掩蔽加载/存储仅关注掩模元素的符号位.

一旦你将8位掩码广播到dword元素,这只是1 uop(在Skylake上).

vpbroadcastd  ymm0, dword [mask]

vpsllvd       ymm0, ymm0, [vec of 24, 25, 26, 27, 28, 29, 30, 31]  ; high bit of each element = corresponding bit of the mask

;vpsrad        ymm0, ymm0, 31                          ; broadcast the sign bit of each element to the whole element
;vpsllvd + vpsrad has no advantage over vpand / vpcmpeqb, so don't use this if you need all the bits set.
Run Code Online (Sandbox Code Playgroud)

vpbroadcastb和内存负载一样便宜(在Intel CPU和Ryzen上根本没有ALU uop).(较窄的广播,就像x & mask == mask在英特尔上采用ALU洗牌,但也许不在Ryzen上.)

Haswell/Broadwell(3 uops,有限的执行端口)的变速稍微贵一些,但与Skylake的即时计数一样便宜!(在端口0或1上1个uop.)在Ryzen上,它们也只有2个uop(任何256b操作的最小值),但是具有3c延迟和每4c吞吐量1个.

有关perf信息,请参阅标记wiki,尤其是Agner Fog的insn表.

对于64位元素,请注意算术右移仅适用于16位和32位元素大小.如果您希望将整个元素设置为全零 - 全部为4位 - > 64位元素,请使用不同的策略.

使用内在函数:

__m256i bitmap2vecmask(int m) {
    const __m256i vshift_count = _mm256_set_epi32(24, 25, 26, 27, 28, 29, 30, 31);
    __m256i bcast = _mm256_set1_epi32(m);
    __m256i shifted = _mm256_sllv_epi32(bcast, vshift_count);  // high bit of each element = corresponding bit of the mask
    return shifted;

    // use _mm256_and and _mm256_cmpeq if you need all bits set.
    //return _mm256_srai_epi32(shifted, 31);             // broadcast the sign bit to the whole element
}
Run Code Online (Sandbox Code Playgroud)

在循环内部,LUT可能值得缓存占用空间,具体取决于循环中的指令混合.特别是对于64位元素大小,其缓存占用空间不大,但甚至可能是32位.


另一个选择,而不是变量移位,是使用BMI2将每个位解压缩为高位中具有该掩码元素的字节,然后vpbroadcastd:

; 8bit mask bitmap in eax, constant in rdi

pdep      rax, rax, rdi   ; rdi = 0b1000000010000000... repeating
vmovq     xmm0, rax
vpmovsxbd ymm0, xmm0      ; each element = 0xffffff80 or 0

; optional
;vpsrad    ymm0, ymm0, 8   ; arithmetic shift to get -1 or 0
Run Code Online (Sandbox Code Playgroud)

如果你已经在整数寄存器中有掩码(无论如何你必须vpbroadcastb y,mem/ vpmovsx单独),那么即使在变量计数变化便宜的Skylake上,这种方式可能更好.

如果你的掩码从内存开始,另一个ALU方法(vmovq直接进入向量)可能更好,因为广播负载是如此便宜.

注意vpbroadcastdRyzen的6个依赖uops(18c延迟,18c吞吐量),所以这个方法在Ryzen上很糟糕,即使你的掩码确实以整数reg开始.

(未来的读者,可以在这个内在版本中自由编辑.编写asm更容易,因为它的输入更少,并且asm助记符更容易阅读(vpbroadcastd整个地方没有愚蠢的杂乱).)


归档时间:

查看次数:

1696 次

最近记录:

6 年 前