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

How to create a 8 bit mask from lsb of __m64 value?

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

没有直接的方法,但显然你可以简单地将 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

的需求

如果您有高效的 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 执行此操作的循环将不会在等待内存时保持其执行单元忙碌。