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

Posted

技术标签:

【中文标题】如何从 __m64 值的 lsb 创建一个 8 位掩码?【英文标题】:How to create a 8 bit mask from lsb of __m64 value? 【发布时间】:2018-08-30 11:40:43 【问题描述】:

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

【问题讨论】:

您能否为您的输入显示一个示例输出?要将0,1,0,1 转换为5ULL 吗?通常你能做的最好的就是用一个简单的具有已知边界的 for 循环来表达它。编译器将能够针对您编译到的架构对其进行矢量化(例如,如果稍后您想将程序移植到 ARM,则使用 Intel 内在函数将没有用处)。 是的.. 对于上面的例子,我想将 0,1,0,1,0,1,0,1 转换为 85。 我不想移植到 ARM 平台。我想将其转换为整数,然后使用该整数作为索引来查找表。所以... 【参考方案1】:

没有直接的方法可以做到这一点,但显然你可以简单地将 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 的需求

【讨论】:

轮班不贵吗?在性能报告中,我看到这种转变显示为昂贵?虽然 AMD 优化手册说它有一个周期延迟。 @yadhu 转变本身不应该很慢,您能否确认实际上是转变而不是通常的“成本分配错误”将责任从缓慢的负载转移到使用的操作负载的结果? 我也有同样的怀疑,有没有办法避免“错误分配成本”而转移责任。 @yadhu 我不知道,但您可以做一些实验来区分不同的成本来源,例如查看消除班次的效果【参考方案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 执行此操作的循环不会使其执行单元保持忙碌在等待内存时。

【讨论】:

以上是关于如何从 __m64 值的 lsb 创建一个 8 位掩码?的主要内容,如果未能解决你的问题,请参考以下文章

从 uint8_t* 到 uint32_t 的无效转换 - 从 32 位架构迁移到 64 位架构时?

将 16 位值的 __m256i 打包(饱和)到 8 位值的 __m128i?

将__m256i的前N位或后N位设置为1,其余为0的有效方法

将 __m256i 寄存器转换为 uint64_t 位掩码,以便每个字节值处的值是输出中的设置位

将 64 位 int 值写入 NSOutputStream

正确使用 _mm256_maskload_ps 将少于 8 个浮点数加载到 __m256