int SWAR(unsigned int i)
{
i = i - ((i >> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}
我已经看到这个代码在32位整数中计算了等于1
的位数,我注意到它的性能优于__builtin_popcount
但我无法理解它的方式的工作原理。
有人可以详细解释这段代码的工作原理吗?
答案 0 :(得分:34)
好的,让我们逐行完成代码:
i = i - ((i >> 1) & 0x55555555);
首先,常量0x55555555
的意义在于,使用Java / GCC样式binary literal notation编写的),
0x55555555 = 0b01010101010101010101010101010101
即,所有奇数位(将最低位计为位1 =奇数)为1
,所有偶数位为0
。
表达式((i >> 1) & 0x55555555)
因此将i
的位右移1,然后将所有偶数位设置为零。 (等效地,我们可以首先将i
的所有奇数位设置为零,& 0xAAAAAAAA
和然后将结果右移一位。)为方便起见,我们称之为中间值j
。
当我们从原始j
中减去此i
时会发生什么?好吧,让我们看看如果i
只有两个位会发生什么:
i j i - j
----------------------------------
0 = 0b00 0 = 0b00 0 = 0b00
1 = 0b01 0 = 0b00 1 = 0b01
2 = 0b10 1 = 0b01 1 = 0b01
3 = 0b11 1 = 0b01 2 = 0b10
喂!我们设法计算了两位数的位数!
好的,但如果i
设置了两位以上怎么办?事实上,很容易检查上面的表格仍会给出i - j
的最低两位,,第三和第四位以及第五和第六位也是如此等等。特别是:
尽管>> 1
,i - j
的最低两位不受i
的第三位或更高位的影响,因为它们将被j
屏蔽掉。 1 {}由& 0x55555555
;以及
因为j
的最低两位永远不会比i
的数值更大,所以减法永远不会从i
的第三位借用:因此,i
的最低两位也不会影响i - j
的第三位或更高位。
事实上,通过重复相同的参数,我们可以看到该行的计算实际上将上表应用于{{1}中16个两位块的每个 } 并行。也就是说,在执行此行之后,新值i
的最低两位现在将包含原始值{{1}中的相应位中设置的数字位接下来的两位也是如此,等等。
i
与第一行相比,这个很简单。首先,请注意
i
因此,i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
采用上面计算的两位数计算并抛弃其中的每一位计数,而0x33333333 = 0b00110011001100110011001100110011
在转移i & 0x33333333
之后执行相同的两位。然后我们将结果一起添加。
因此,实际上,这一行的作用是获取原始输入的最低两位和第二低两位的bitcounts,在前一行计算,并将它们加在一起得到最低的bitcount < em>输入的四个位。而且,它同样为所有输入的8个四位块(=十六进制数字)并行执行此操作。
(i >> 2) & 0x33333333
好的,这里发生了什么?
嗯,首先,i
与前一行完全相同,不同之处在于它将相邻的四位 bitcounts一起添加到每个八的bitcounts中-bit 输入的块(即字节)。 (这里,与前一行不同,我们可以在添加之外移动return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
,因为我们知道8位bitcount永远不会超过8,因此将适合四位而不会溢出。)
现在我们有一个由4个8位字节组成的32位数字,每个字节保存原始输入字节中的1位数。 (我们称这些字节为(i + (i >> 4)) & 0x0F0F0F0F
,&
,A
和B
。)那么当我们将此值(我们称之为C
)乘以{时会发生什么? {1}}?
好吧,自D
以来,我们有:
k
因此,结果的最高字节最终成为:
的总和0x01010101
字词加上0x01010101 = (1 << 24) + (1 << 16) + (1 << 8) + 1
项加上k * 0x01010101 = (k << 24) + (k << 16) + (k << 8) + k
项,加上k
术语而导致的第四个和最低字节的值。(一般情况下,也可能有较低字节的进位,但由于我们知道每个字节的值最多为8,因此我们知道加法永远不会溢出并创建进位。)
也就是说,k << 8
的最高字节最终是输入的所有字节的bitcounts的总和,即32位输入数的总bitcount。最后k << 16
然后只是将此值从最高字节向下移动到最低字节。
Ps。只需将k << 24
更改为k * 0x01010101
,将>> 24
更改为{{1>即可轻松将此代码扩展为64位整数}}。实际上,相同的方法甚至适用于128位整数;然而,256位需要添加一个额外的移位/添加/屏蔽步骤,因为数字256不再完全适合8位字节。
答案 1 :(得分:10)
我更喜欢这个,它更容易理解。
x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x & 0x0f0f0f0f) + ((x >> 4) & 0x0f0f0f0f);
x = (x & 0x00ff00ff) + ((x >> 8) & 0x00ff00ff);
x = (x & 0x0000ffff) + ((x >> 16) &0x0000ffff);
答案 2 :(得分:1)
这是对Ilamari's answer的评论。 由于格式问题,我把它作为答案:
i = i - ((i >> 1) & 0x55555555); // (1)
此行源自此easier to understand line:
i = (i & 0x55555555) + ((i >> 1) & 0x55555555); // (2)
如果我们打电话
i = input value
j0 = i & 0x55555555
j1 = (i >> 1) & 0x55555555
k = output value
我们可以重写(1)和(2)以使解释更清楚:
k = i - j1; // (3)
k = j0 + j1; // (4)
我们想证明(3)可以从(4)得出。
i
可以写为偶数和奇数位的加法(将最低位计为第1位=奇数):
i = iodd + ieven =
= (i & 0x55555555) + (i & 0xAAAAAAAA) =
= (i & modd) + (i & meven)
由于meven
掩码清除i
的最后一位,
最后的等式可以这样写:
i = (i & modd) + ((i >> 1) & modd) << 1 =
= j0 + 2*j1
那是:
j0 = i - 2*j1 (5)
最后,将(5)替换为(4)我们实现(3):
k = j0 + j1 = i - 2*j1 + j1 = i - j1