我想根据任意掩码打包这些位

I want to pack the bits based on arbitrary mask

假设数据是 1011 1001,掩码是 0111 0110,那么你有:

input data:       1011 1001
input mask:       0111 0110
apply mask:       0011 0000 (based on `input mask`)
bits selected:    -011 -00- (based on `input mask`)
right packed:     ---0 1100
expected result:  0000 1100 (set left `8 - popcount('input mask')` bits to zero) 

所以最后的输出是0000 1100(注意左边3个未指定的位置用0补齐)

你可以看到 input mask 中的位为 1 时 input data 中对应的值为 selected (在 bits selected 上面)然后所有 selected 位被连续打包在结果的最低有效位开始(如上面 right packed 所示)。最后,将打包后剩下的任何最左边的位设置为0(将有8 - popcount(mask)个这样的位)。

显而易见的选择是旋转和 select 但是这将消耗 5 次操作,因为掩码有 5 位。我可以一步完成吗?

注:

  1. 掩码可以是任意nON的任何东西(在上面 例如 n=5)。您所知道的是 ON 中的位数 面具和面具本身。掩码将继续变化 nON.

  2. 在上面的示例中,我使用了 8 位的数据和掩码,但实际上 usage 它可以是 8、16、32、64 和 128 位。

如果您的目标是 x86,大多数编译器将具有 pdep 的内在函数(parallel bit deposit) instruction which directly performs the operation you want, in hardware, at a rate of 1 per cycle (3 cycles latency)1, on Intel hardware that supports it. For example, gcc offers it 作为 _pdep_u32_pdep_u64 内在函数。

不幸的是,在 AMD Ryzen(唯一支持 BMI2 的 AMD 硬件)上,这个操作非常慢:每 18 个周期一个。如果它们对您很重要,您可能希望有一个单独的 code-path 来支持 non-Intel 平台。

如果您不在 x86,您可以找到这些选项的通用实现 here - 您想要的特定操作是 expand_right - 其他部分可能非常感兴趣,因为它特别涵盖了您处理 word-sized 元素的简单情况。

实际上,如果您真的要处理 8 位数据和掩码值,您可能只使用预先计算的查找 table - 要么是一个大的 8 位 x 8 位 = 65k,它涵盖所有 {data, mask} 组合,直接给你答案,或者一个包含所有 mask 值并给你一些系数的 256 项组合,用于简单的 bit-shifting 计算或 multiplication-based 代码.

FWIW,我不确定你如何使用 5 个循环指令轻松完成它,因为看起来天真的解决方案需要每个位 1 个循环指令,无论是否设置(因此对于 8 字长位,7 或 8 个旋转 2 指令)。


1当然,原则上性能取决于硬件,但在所有实现它的主流Intel CPU上,它是1个周期的吞吐量,3个周期的延迟(不确定关于 AMD)。

2只有7次循环,因为"rotate of 0"最低位的操作显然可以省略。