首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >基于BitMask在数组中设置值的内在特性

基于BitMask在数组中设置值的内在特性
EN

Stack Overflow用户
提问于 2018-01-31 09:04:24
回答 1查看 1.6K关注 0票数 0

在输入数组中的所有位置,在所提供的BitMask中对应位置有1位的位置,是否有一个内部设置值?

10101010是位掩码

值为121

它将设置价值121的0、2、4、6的头寸。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-01-31 09:39:06

和AVX512一起,是的。在AVX512,蒙面商店是一流的业务。

将位掩码用作向量存储到数组的AVX512掩码,使用_mm512_mask_storeu_epi8 (void* mem_addr, __mmask64 k, __m512i a) vmovdqu8。(AVX512BW.使用AVX512F,您只能使用32或64位的元素大小。)

代码语言:javascript
复制
#include <immintrin.h>
#include <stdint.h>

void set_value_in_selected_elements(char *array, uint64_t bitmask, uint8_t value) {
    __m512i broadcastv = _mm512_set1_epi8(value);
    // integer types are implicitly convertible to/from __mmask types
    // the compiler emits the KMOV instruction for you.
    _mm512_mask_storeu_epi8 (array, bitmask, broadcastv);
}

这会将)编译为:

代码语言:javascript
复制
    vpbroadcastb    zmm0, edx
    kmovq   k1, rsi
    vmovdqu8        ZMMWORD PTR [rdi]{k1}, zmm0
    vzeroupper
    ret

如果要在位图为零的元素中写入零,可以使用零掩蔽移动从掩码创建常量并将其存储,或者使用AVX512BW或DQ __m512i _mm512_movm_epi8(__mmask64 )创建0/ -1向量。其他元素大小也可用。但是,当数组大小不是向量宽度的倍数时,使用蒙面存储可以安全地使用它,因为未修改的元素不会被读取/重写或其他任何东西;它们确实没有被触摸。(不过,如果任何未触及的元素在真正的存储上发生故障,CPU可以获得缓慢的微码辅助。)

没有AVX512,您仍然需要“一个内在的”(单数)。

pdep,您可以使用它将位图展开为字节映射。有关使用my AVX2 left-packing answermask中的每个位解压缩为一个字节的示例,请参阅_pdep_u64(mask, 0x0101010101010101);。这在uint64_t中给出了8个字节。在C中,如果在该数组和数组之间使用union,那么它将给出一个0/1元素的数组。(当然,索引数组需要编译器发出移位指令,如果它还没有先在某个地方溢出的话)。您可能只想将memcpy uint64_t放入一个永久数组中。)

但是在更一般的情况下(更大的位图),甚至当您想要根据位掩码混合新的值时,也应该使用多个本质来实现pmovmskb的逆,并使用它来混合。(参见下面的无一节)

通常,如果数组适合64位(例如,8元素char数组),则可以使用pdep。或者,如果它是一个4位小咬的数组,那么你可以做一个16位的掩码,而不是8位。

否则,就没有单一的指令,也就没有内在的指令。对于较大的位图,您可以将其处理为8位块,并将8字节块存储到数组中。

如果数组元素大于8位(而且您没有AVX512),则可能仍然应该用pdep将位展开为字节,然后使用[v]pmovzx从字节扩展到dword或向量中的任何东西。例如:

代码语言:javascript
复制
// only the low 8 bits of the input matter
__m256i bits_to_dwords(unsigned bitmap) {
    uint64_t mask_bytes = _pdep_u64(bitmap, 0x0101010101010101);  // expand bits to bytes
    __m128i byte_vec = _mm_cvtsi64x_si128(mask_bytes);
    return _mm256_cvtepu8_epi32(byte_vec);
}

如果您想让元素不被修改,而不是将它们设置为零(位掩码在位掩码中为零--,或与前面的内容一起使用,而不是赋值/存储),则为。

在C/ C++ (与asm相比)中表示这是相当不方便的。要将8个字节从uint64_t复制到char数组中,您可以(而且应该)只使用memcpy (以避免由于指针混叠或对齐uint64_t*而导致的任何未定义行为)。这将用现代编译器编译成一个8字节的存储区。

但是要将它们放入其中,则必须对uint64_t的字节编写一个循环,或者将char数组转换为uint64_t*。这通常很好,因为char*可以别名任何东西,这样以后读取char数组就不会有任何严格的别名UB了。但是,如果编译器假设uint64_t*在自动向量化时是对齐的,那么即使在x86上也会出现问题。Why does unaligned access to mmap'ed memory sometimes segfault on AMD64?

分配的值不是0/ 1

使用乘0xFF将0/1字节的掩码转换为0/ -1掩码,然后使用uint64_t将值广播到所有字节位置。

如果不想将元素设置为零或value=121,那么即使数组中有字节元素,也应该使用SSE2 / SSE4或AVX2。使用字节掩码作为控制向量加载旧内容vpblendvbset1(121) .

vpblendvb只使用每个字节的高位,所以您的pdep常数可以是0x8080808080808080,将输入位分散到每个字节的高位,而不是低位。(所以你不需要被0xFF乘以就可以得到一个和掩码)。

如果元素是dword或更大,则可以使用_mm256_maskstore_epi32。(在将掩码从字节扩展到dword时,使用pmovsx而不是zx复制符号位)。这可能是对变量混合+总是读/重写的一个额外的胜利。Is it possible to use SIMD instruction for replace?

pdep

pdep在Ryzen上速度很慢,即使是英特尔,这也不是最好的选择。

另一种方法是将位掩码转换为矢量掩码:is there an inverse instruction to the movemask instruction in intel avx2?

epi8 (VPMOVMSKB)?

也就是说,将你的位图广播到向量的每一个位置(或者在对应的字节中对位图的正确位进行洗牌),并使用SIMD并为该字节屏蔽适当的位。然后,对和掩码使用pcmpeqb/w/d来查找设置其位的元素。

如果不想在位图为零的地方存储零,则可能需要加载/混合/存储。

使用比较掩码在您的value上混合,例如与_mm_blendv_epi8或256位AVX2版本。您可以用16位块处理位图,只需一个pshufb就可以生成16字节的向量,将其字节发送到正确的元素。

但是,即使多线程的位图不相交,多个线程在同一数组上同时执行此操作也是不安全的,除非您使用蒙面存储。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48538254

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档