C 将字段中的位扩展到掩码中的所有(重叠和相邻)设置位的最快方法?

C 将字段中的位扩展到掩码中的所有(重叠和相邻)设置位的最快方法?,c,assembly,x86,sse,avx,C,Assembly,X86,Sse,Avx,假设我有两个名为IN和MASK的二进制输入。实际字段大小可能为32到256位,具体取决于用于完成任务的指令集。两个输入都会改变每次呼叫 Inputs: IN = ...1100010010010100... MASK = ...0001111010111011... Output: OUT = ...0001111010111000... 编辑:来自一些评论讨论的另一个示例结果 IN = ...11111110011010110... MASK = ...01011011001111

假设我有两个名为IN和MASK的二进制输入。实际字段大小可能为32到256位,具体取决于用于完成任务的指令集。两个输入都会改变每次呼叫

Inputs:
IN   = ...1100010010010100...
MASK = ...0001111010111011...
Output:
OUT  = ...0001111010111000...
编辑:来自一些评论讨论的另一个示例结果

IN   = ...11111110011010110...
MASK = ...01011011001111110...
Output:
OUT  = ...01011011001111110...
我想得到1位IN所在的相邻1位掩码。(这类操作有一个通用术语吗?也许我没有正确地表达我的搜索。)我正在试图找到一种更快的方法。我愿意使用任何x86或x86 SIMD扩展,这些扩展可以在最短的cpu周期内完成。更广泛的数据类型SIMD是首选,因为它允许我一次处理更多数据

我提出的最简单的解决方案是以下伪代码,它手动向左移位,直到没有更多匹配位,然后重复向右移位:

// (using the variables above)
testL = testR = OUT = (IN & MASK);

LoopL:
testL = (testL << 1) & MASK;
if (testL != 0) {
    OUT = OUT | testL;
    goto LoopL;
}

LoopR:
testR = (testR >> 1) & MASK;
if (testR != 0) {
    OUT = OUT | testR;
    goto LoopR;
}

return OUT;
//(使用上述变量)
testL=testR=OUT=(IN&MASK);
LoopL:
testL=(testL>1)和MASK;
如果(testR!=0){
OUT=OUT | testR;
去活套;
}
返回;

以下方法只需要一个循环,迭代次数等于找到的“组”数量。 我不知道它是否会比你的方法更有效;每个迭代中有6个算术/位运算

在伪代码(类C)中:

下面是它的工作原理,使用11010111作为示例掩码,一步一步:

OUT = 0

a = MASK        11010111
c = a & (-a)    00000001   keeps rightmost one only
d = a + c       11011000   clears rightmost group (and set the bit to its immediate left)
e = a & ~d      00000111   keeps rightmost group only

if (e & IN) OUT |= e;      adds group to OUT

a = a ^ e       11010000   clears rightmost group, so we can proceed with the next group
c = a & (-a)    00010000
d = a + c       11100000
e = a & ~d      00010000

if (e & IN) OUT |= e;

a = a ^ e       11000000
c = a & (-a)    01000000
d = a + c       00000000   (ignoring carry when adding)
e = a & ~d      11000000

if (e & IN) OUT |= e;

a = a ^ e       00000000   done
正如@PeterCordes所指出的,可以使用x86 BMI1指令优化某些操作:

  • c=a&(-a)
  • e=a&~d

这种方法适用于不支持位反转的处理器体系结构。在有专门指令反转整数中位顺序的体系结构上,更有效。

我猜@fuz comment的做法是正确的。 下面的示例显示了下面的SSE和AVX2代码的工作原理。 该算法以
IN_reduced=IN&MASK开始,因为我们不感兴趣
在
in
位中,位于
MASK
0
的位置

IN=。0 0 0 0 . . . . p q r s。
面具=。0 1 1 1 1 0 . . 0 1 1 1 1 0 . . 
IN_reduced=IN&MASK=。0 0 0 0 0 0 . . 0 p q r s 0。
如果任何
p q r s
位为
1
,则_reduced+掩码中的
具有进位
1
在位置
X
,位于 请求的连续位

MASK=。0 1 1 1 1 0 . . 0 1 1 1 1 0 . . 
IN_reduced=。0 0 0 0 0 0 . . 0 p q r s 0。
IN_减少+遮罩=。0 1 1 1 1 . . . 1.
X
(IN_reduced+MASK)>>1=。0 1 1 1 1 . . . 1.
使用
>1
时,此进位
1
与位
p
(连续位的第一位)。 现在,
(IN_reduced+MASK)>>1
实际上是
IN_reduced
MASK
的平均值。 为了避免可能的加法溢出,我们使用以下方法 平均值:
avg(a,b)=(a&b)+(a^b)>>1
(见@Harold的评论, 另请参见和。) 使用
average=avg(单位减少,掩码)
我们得到

MASK=。0 1 1 1 1 0 . . 0 1 1 1 1 0 . . 
IN_reduced=。0 0 0 0 0 0 . . 0 p q r s 0。
平均值=。0 1 1 1 1 . . . 1.
掩码>>1=。0 1 1 1 1 0 . . 0 1 1 1 1 0 .  
前导位=(~(MASK>>1))&平均值=。0 0 0 0 0 . . . 1 0 0 0 0 . .  
我们可以使用
前导位=(~(MASK>>1))&平均值
,因为
掩码>>1
在位置处为零 进位的计算 我们感兴趣的

正常加法时,进位从右向左传播。这里我们使用 反向加法:从左到右进位。 反向添加
掩码
前导位
rev_added=位交换(位交换(掩码)+位交换(前导位))
, 这会将位置零 被通缉的职位。 使用
OUT=(~rev_added)和MASK
我们得到了结果

MASK=。0 1 1 1 1 0 . . 0 1 1 1 1 0 . . 
前导字节=。0 0 0 0 0 . . . 1 0 0 0 0 . .  
添加的版本(掩码、前导位)=。1 1 1 1 0 . . . 0 0 0 0 1 . .
输出=~rev_添加和掩码=。0 0 0 0 0 0 . . . 1 1 1 1 0 . .
该算法没有经过彻底测试,但输出看起来还可以


下面的代码块包含两个单独的代码: 上半部分是SSE代码, 下半部分是AVX2代码。 (为了避免 用两个大代码块将答案夸大。) SSE算法适用于2 x 64位元素,AVX2版本适用于4 x 64位元素

在gcc 9.1中,算法, 除了4
vmovdqa
-s用于加载一些常量之外,这些常量可能 在实际应用程序中从循环中提升(内联后)。 这29条指令很好地混合了执行的9个随机操作(
vpshufb
) 在英特尔Skylake上的端口5(p5)上,以及许多其他通常可能 在p0、p1或p5上执行

因此,每个周期大约有3条指令的性能是可能的。 在这种情况下,吞吐量大约为1个函数调用(内联) 每10个周期。在AVX2的情况下,这意味着每分钟有4个
uint64\u t
OUT
结果 大约10个周期

请注意,性能独立于数据(!),这是一个很好的例子 我认为这个答案的好处。解决方案是无分支、无环的,并且 不能忍受f
OUT = 0

a = MASK        11010111
c = a & (-a)    00000001   keeps rightmost one only
d = a + c       11011000   clears rightmost group (and set the bit to its immediate left)
e = a & ~d      00000111   keeps rightmost group only

if (e & IN) OUT |= e;      adds group to OUT

a = a ^ e       11010000   clears rightmost group, so we can proceed with the next group
c = a & (-a)    00010000
d = a + c       11100000
e = a & ~d      00010000

if (e & IN) OUT |= e;

a = a ^ e       11000000
c = a & (-a)    01000000
d = a + c       00000000   (ignoring carry when adding)
e = a & ~d      11000000

if (e & IN) OUT |= e;

a = a ^ e       00000000   done
Example 1 
IN           00000000 00000000 00000000 00000000 00000000 00000001 11111100 11010110 00000000 00000000 00000000 00000000 00000000 00000000 11000100 10010100
MASK         00000000 00000000 00000000 00000000 00000000 00000000 10110110 01111110 00000000 00000000 00000000 00000000 00000000 00000000 00011110 10111011
IN_reduced   00000000 00000000 00000000 00000000 00000000 00000000 10110100 01010110 00000000 00000000 00000000 00000000 00000000 00000000 00000100 10010000
tmp          00000000 00000000 00000000 00000000 00000000 00000000 00000010 00101000 00000000 00000000 00000000 00000000 00000000 00000000 00011010 00101011
tmp_div2     00000000 00000000 00000000 00000000 00000000 00000000 00000001 00010100 00000000 00000000 00000000 00000000 00000000 00000000 00001101 00010101
average      00000000 00000000 00000000 00000000 00000000 00000000 10110101 01101010 00000000 00000000 00000000 00000000 00000000 00000000 00010001 10100101
MASK_div2    00000000 00000000 00000000 00000000 00000000 00000000 01011011 00111111 00000000 00000000 00000000 00000000 00000000 00000000 00001111 01011101
leading_bits 00000000 00000000 00000000 00000000 00000000 00000000 10100100 01000000 00000000 00000000 00000000 00000000 00000000 00000000 00010000 10100000
rev_added    00000000 00000000 00000000 00000000 00000000 00000000 01001001 00000001 00000000 00000000 00000000 00000000 00000000 00000000 00000001 01000111
OUT          00000000 00000000 00000000 00000000 00000000 00000000 10110110 01111110 00000000 00000000 00000000 00000000 00000000 00000000 00011110 10111000

IN           00000000 00000000 00000000 00000000 00000000 00000001 11111100 11010110 00000000 00000000 00000000 00000000 00000000 00000000 11000100 10010100
MASK         00000000 00000000 00000000 00000000 00000000 00000000 10110110 01111110 00000000 00000000 00000000 00000000 00000000 00000000 00011110 10111011
OUT          00000000 00000000 00000000 00000000 00000000 00000000 10110110 01111110 00000000 00000000 00000000 00000000 00000000 00000000 00011110 10111000


Example 2 
IN           10000010 01001010 00001000 00001000 00010000 00000010 00000001 11100011 00000000 00000000 00000000 00000000 00000000 00000001 11111100 11010111
MASK         11100111 10101110 11111100 00000001 11011111 10110111 11000111 11000001 00000000 00000000 00000000 00000000 00000000 00000000 10110110 01111111
IN_reduced   10000010 00001010 00001000 00000000 00010000 00000010 00000001 11000001 00000000 00000000 00000000 00000000 00000000 00000000 10110100 01010111
tmp          01100101 10100100 11110100 00000001 11001111 10110101 11000110 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000010 00101000
tmp_div2     00110010 11010010 01111010 00000000 11100111 11011010 11100011 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 00010100
average      10110100 11011100 10000010 00000000 11110111 11011100 11100100 11000001 00000000 00000000 00000000 00000000 00000000 00000000 10110101 01101011
MASK_div2    01110011 11010111 01111110 00000000 11101111 11011011 11100011 11100000 00000000 00000000 00000000 00000000 00000000 00000000 01011011 00111111
leading_bits 10000100 00001000 10000000 00000000 00010000 00000100 00000100 00000001 00000000 00000000 00000000 00000000 00000000 00000000 10100100 01000000
rev_added    00010000 01100001 00000010 00000001 11000000 01110000 00100000 00100000 00000000 00000000 00000000 00000000 00000000 00000000 01001001 00000000
OUT          11100111 10001110 11111100 00000000 00011111 10000111 11000111 11000001 00000000 00000000 00000000 00000000 00000000 00000000 10110110 01111111

IN           10000010 01001010 00001000 00001000 00010000 00000010 00000001 11100011 00000000 00000000 00000000 00000000 00000000 00000001 11111100 11010111
MASK         11100111 10101110 11111100 00000001 11011111 10110111 11000111 11000001 00000000 00000000 00000000 00000000 00000000 00000000 10110110 01111111
OUT          11100111 10001110 11111100 00000000 00011111 10000111 11000111 11000001 00000000 00000000 00000000 00000000 00000000 00000000 10110110 01111111