我想根据任意掩码打包这些位
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 位。我可以一步完成吗?
注:
掩码可以是任意n
位ON
的任何东西(在上面
例如 n=5
)。您所知道的是 ON
中的位数
面具和面具本身。掩码将继续变化 n
位 ON
.
在上面的示例中,我使用了 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"最低位的操作显然可以省略。
假设数据是 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 位。我可以一步完成吗?
注:
掩码可以是任意
n
位ON
的任何东西(在上面 例如n=5
)。您所知道的是ON
中的位数 面具和面具本身。掩码将继续变化n
位ON
.在上面的示例中,我使用了 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"最低位的操作显然可以省略。