假设数据是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个未指定的位置是零填充的).
您可以看到,只要屏蔽位为1,input mask
就会选择相应的值(在input data
上面),然后所有选中的位在结果的最低有效位中连续打包(如上所示bits selected
).最后,在打包后留下的任何最左边的位被设置为0(将存在right packed
这样的位).
明显的选择是旋转和选择,但这将消耗5个操作,因为掩码有5位.我可以一步完成吗?
注意:
掩码可以是任意8 - popcount(mask)
位的任何东西n
(在上面的例子中ON
).您所知道的是n=5
掩码中的位数和掩码本身.掩码将继续随ON
位变化n
.
在上面的示例中,我使用了8位的数据和掩码,但在实际使用中,它可以是8位,16位,32位,64位和128位.
BeeOnRope.. 7
如果您的目标是x86
大多数编译器将具有pdep
(并行位存储)指令的内在函数,该指令直接执行您想要的操作,在硬件中,以每周期1个(3个周期延迟)1的速率执行,在支持它的英特尔硬件上.例如,GCC 提供它作为_pdep_u32
和_pdep_u64
内在功能.
不幸的是,在AMD Ryzen(唯一支持BMI2的AMD硬件)上,此操作非常慢:每18个周期一个.您可能希望有一个单独的代码路径来支持非英特尔平台,如果它们对您很重要的话.
如果你不在x86
,你可以在这里找到这些选项的通用实现- 你想要的具体操作expand_right
- 而这个其他部分可能会引起极大兴趣,因为它特别涵盖了你处理单词的简单情况 -大小的元素.
实际上,如果你真的在处理8位数据和掩码值,你可能只使用一个预先计算的查找表 - 大8位x 8位= 65k,它涵盖所有{data, mask}
组合,直接给你答案,或者256条输入,覆盖所有mask
值,并为简单的位移计算或基于乘法的代码提供一些系数.
FWIW,我不知道如何使用5个旋转指令轻松完成,因为看起来天真的解决方案需要每个位1个旋转指令,无论是否设置(因此对于8位,7或8的字大小)旋转2条指令).
1当然,性能原则上取决于硬件,但在实现它的所有主流Intel CPU上,它的1个周期吞吐量,3个周期的延迟(不确定AMD).
2只有7旋转,因为最低位的"0的旋转"操作显然可以省略.
如果您的目标是x86
大多数编译器将具有pdep
(并行位存储)指令的内在函数,该指令直接执行您想要的操作,在硬件中,以每周期1个(3个周期延迟)1的速率执行,在支持它的英特尔硬件上.例如,GCC 提供它作为_pdep_u32
和_pdep_u64
内在功能.
不幸的是,在AMD Ryzen(唯一支持BMI2的AMD硬件)上,此操作非常慢:每18个周期一个.您可能希望有一个单独的代码路径来支持非英特尔平台,如果它们对您很重要的话.
如果你不在x86
,你可以在这里找到这些选项的通用实现- 你想要的具体操作expand_right
- 而这个其他部分可能会引起极大兴趣,因为它特别涵盖了你处理单词的简单情况 -大小的元素.
实际上,如果你真的在处理8位数据和掩码值,你可能只使用一个预先计算的查找表 - 大8位x 8位= 65k,它涵盖所有{data, mask}
组合,直接给你答案,或者256条输入,覆盖所有mask
值,并为简单的位移计算或基于乘法的代码提供一些系数.
FWIW,我不知道如何使用5个旋转指令轻松完成,因为看起来天真的解决方案需要每个位1个旋转指令,无论是否设置(因此对于8位,7或8的字大小)旋转2条指令).
1当然,性能原则上取决于硬件,但在实现它的所有主流Intel CPU上,它的1个周期吞吐量,3个周期的延迟(不确定AMD).
2只有7旋转,因为最低位的"0的旋转"操作显然可以省略.