如何从__m64值的lsb创建8位掩码?

时间:2018-08-30 11:40:44

标签: c++ simd avx avx2 mmx

我有一个用例,其中我有一个位数组,每个位都表示为8位整数,例如uint8_t data[] = {0,1,0,1,0,1,0,1};我想通过仅提取每个值的lsb来创建一个整数。我知道使用int _mm_movemask_pi8 (__m64 a)函数可以创建掩码,但是此内在函数仅占用字节的msb而不是lsb。是否有类似的固有或有效方法来提取lsb以创建单个8位整数?

2 个答案:

答案 0 :(得分:5)

没有直接的方法,但是显然您可以简单地将lsb移入msb,然后将其提取:

_mm_movemask_pi8(_mm_slli_si64(x, 7))

这几天使用MMX很奇怪,应该避免。

这是SSE2版本,仍仅读取8个字节:

int lsb_mask8(uint8_t* bits) {
    __m128i x = _mm_loadl_epi64((__m128i*)bits);
    return _mm_movemask_epi8(_mm_slli_epi64(x, 7));
}

使用SSE2而不是MMX可以避免使用EMMS

答案 1 :(得分:2)

如果您有高效的BMI2 pext(例如,Haswell和更新的版本,与AVX2相同),请对问题的另一方向使用{wim答案的反方向(How to efficiently convert an 8-bit bitmap to array of 0/1 integers with x86 SIMD)。

unsigned extract8LSB(uint8_t *arr) {
    uint64_t bytes;
    memcpy(&bytes, arr, 8);
    unsigned LSBs = _pext_u64(bytes ,0x0101010101010101);
    return LSBs;
}

compiles like you'd expect至qword加载+一条pext指令。内联后,编译器将0x01...常量设置从循环中取消。


pext / pdep在支持它们的Intel CPU上非常有效(3个周期延迟/ 1c吞吐量,1 uop,与乘法相同)。但是它们在AMD上效率不高,例如18c延迟和吞吐量。 (https://agner.org/optimize/)。如果您关心AMD,则绝对应该使用@harold的pmovmskb答案。

或者,如果您有8个字节的多个连续块,请使用单个宽矢量进行处理,然后获得32位位图。您可以根据需要将其拆分,或者使用4展开循环,以右移位图以获得所有4个单字节结果。

如果您只是立即将其存储到内存中,那么您应该应该在写入源数据的循环中进行此提取,而不是在单独的循环中进行提取,因此它在高速缓存中仍然很热。 AVX2 _mm256_movemask_epi8是低延迟的单个uop(在Intel CPU上),因此,如果您的数据在L1d缓存中不热,那么 just 这样做的循环将无法保持其执行单元正在等待内存。