I have a byte b
. I am looking for the most efficient bit manipulation to
convert each bit in b
to the first bit of each nibble in a 32 bit int x
.
For example, if b = 01010111
, then x = 0x10101111
I know I can do a brute force approach:
x = (b&1) | (((b>>1)&1)<<4) | ......
Edit: this for an OpenCL kernel for GPU
答案 0 :(得分:1)
正如用户harold在评论中提到的那样,PDEP是完全你想要的指令 - 但它仅在x86上可用(据我所知),并且它在newest AMD chips上有 1 表现可怕。
除此之外,256 x 4字节条目的查找表似乎是合理的 - 代价是缓存子系统的1K压力。由于隐藏的高速缓存未命中成本,你会发现许多聪明人提倡反对LUT - 但如果这个特定操作实际上是“热门”,那么即使考虑到任何额外的未命中,它也可能变得最快。 / p>
与任何LUT解决方案一样,您应特别注意不仅要使用微基准测试,而且要在整个应用程序中对其进行基准测试,以评估内存压力的影响。
您还可以考虑一种折衷分裂LUT解决方案,该解决方案对字节的每个半字节使用一个或两个16项LUT,其结果计算如下:
int32 x = high_lut[(b & 0xF0) >> 4] | low_lut[b & 0xF]
这会将LUT的大小减小~11到32 2 ,因为我们有更少的条目,有些条目可以是2个字节而不是4个字节。
如果您真的想要一点操作解决方案,为了给您的章程留下深刻印象,您可以尝试以下内容:
0x00001111
(低半字节)和0x01111000
(高半字节)将低(高亮度)半字节映射到低(高亮度)的一半4字节的单词,并将结果与or
或add
合并。因此,如果您的字节位为abcd efgh
,则会有abcd abcd abcd abcd efgh efgh efgh efgh
。and
这个结果带有一个掩码,可以选出每个半字节中的位(虽然它通常不会在正确的位置)。掩码类似0x84218421
,结果(二进制)类似于a000 0b00 00c0 000d e000 0f00 00g0 000h
。((x | 0x08880888) - 0x01110111) ^ 0x08880888
。最后一步的基本思想是设置每个半字节的高位,并从半字节中减去1。例如,你有0b00
半字节,它变为1b00 - 1
- 减法包含所有零,并在第一个停止,这是高位(b
是零)或b
如果是一个。因此,您可以根据所选位的值有效地设置高位。请注意,您不需要为a
或e
执行此操作,因为它们已经在正确的位置。
需要最后的xor
因为上面实际上将高位设置为与所选位相反的值,所以我们需要翻转它。
我没有尝试过,所以毫无疑问是错误的,但基本的想法应该是合理的。可能有各种方法可以进一步优化它,但它并不是太糟糕:几次乘法,也许是六次位操作。在具有慢速乘法的平台上,您可以找到第一步的另一种方法,它只使用1次乘法和一些更原始的操作,或者以多次操作为代价为零。
1 吞吐量比英特尔低18倍 - 显然AMD选择不实施电路在硬件中执行PDEP,而是通过一系列更基本的操作来实现它。
2 最大的减少是,如果您为高半字节和低半字节共享一个16项LUT,尽管这需要为高半字节查找的结果进行额外的移位。示例中显示的较小的减少使用两个16入口LUT:一个4字节一个用于高半字节,另一个用于低半字节,并且避免移位。