这是什么?getproccount

bro*_*s94 14 c bit-manipulation go

这段代码发生了什么?从名称和上下文中可以找到机器上的核心数量,但它是如何工作的?什么都有点摆弄?

static int32
getproccount(void)
{
        uintptr buf[16], t;
        int32 r, cnt, i;

        cnt = 0;
        r = runtime·sched_getaffinity(0, sizeof(buf), buf);
        if(r > 0)
        for(i = 0; i < r/sizeof(buf[0]); i++) {
                t = buf[i];
                t = t - ((t >> 1) & 0x5555555555555555ULL);
                t = (t & 0x3333333333333333ULL) + ((t >> 2) & 0x3333333333333333ULL);
                cnt += (int32)((((t + (t >> 4)) & 0xF0F0F0F0F0F0F0FULL) * 0x101010101010101ULL) >> 56);
        }

        return cnt ? cnt : 1;
}
Run Code Online (Sandbox Code Playgroud)

注意:忽略·in runtime·sched_getaffinity,将该行视为一个任意库/系统调用,它执行名称和参数所暗示的操作.(在这种情况下,这个特定的呼叫来自 Go1.4预运行时间写成了交易的C略有变化·).

Jay*_*iya 2

循环for运行与填充到buf数组中的元素一样多的元素。对于每个元素,它计算在该元素中设置的位数(摆弄的位t就是这样做的)并将其添加到 中cnt。最后cnt返回(如果cnt是 0,则返回 1)。

位摆弄的解释:

位摆弄线1

该行t = t - ((t >> 1) & 0x5555555555555555ULL);基本上将 的位分组t为 2 位,并用该组中设置的位数的计数替换每个组。其工作原理如下:

考虑t = ... w x y z其中 w、x、y、z 是各个位。然后

t                 = ... w x y z
t>>1              = ..... w x y
t>>1 & 0x...55ULL = ... 0 w 0 y
Run Code Online (Sandbox Code Playgroud)

从上面可以清楚地看出为什么分组发生在 2 位中(例如,y 和 z 在这里分组在一起)。现在t - ((t >> 1) & 0x5555555555555555ULL)将每组 2 位y z转换为(y-z). 使用 4 种可能性 (00, 01, 10, 11) 的表,我们看到答案是 (00, 01, 01, 10),它与该 2 位组中设置的位数相匹配。

位摆弄线2

继续进行下一个位调整行t = (t & 0x3333333333333333ULL) + ((t >> 2) & 0x3333333333333333ULL);,我们可以看到它将相邻的 2 组以 2 组为一组相加。

t & 0x..33ULL取每组 4 位中最右边的 2 位。
(t>>2) & 0x..33ULL取每组4位中最左边的2位,右移2位。
由于这两组2位是原数中设置的位数,因此将每组4中设置的位数相加位。(即现在,每组 4 位具有最初在这些位置设置的位数)

位摆弄线3

至于最后的一点小插曲cnt += (int32)((((t + (t >> 4)) & 0xF0F0F0F0F0F0F0FULL) * 0x101010101010101ULL) >> 56);,我们可以把它分成几个部分,以便于理解。

(int32)(
  (
    (
      (
        t + (t >> 4)
      ) & 0xF0F0F0F0F0F0F0FULL
    ) * 0x101010101010101ULL
  ) >> 56
)
Run Code Online (Sandbox Code Playgroud)

目前,每组 4 位存储最初在数字中设置的位数。将数字移动 4 并相加会将所有相邻组加在一起。&ing with0x..0F0FULL选出每组 8 位中正确的 4 位。因此,在 的末尾(t + (t >> 4)) & 0x...0F0FULL,有 8 位组,其中包含原始数字中这些位置中的位数。为了简单起见,我们就称呼这个号码吧s = (t + (t >> 4)) & 0x...0F0FULL

我们现在要做的(s * 0x...0101ULL) >> 56。请注意,t和 的s大小都是 64 位。这意味着有 8 组 8 位。通过简单的乘法0x...0101ULL(这只是每个组打开的最右边的位),最左边的组现在将是所有组的总和。通过右移 56(即(64 - 8)),我们将最左边的组移动到最右边的位置。这意味着最右边的 8 位组现在具有 的所有组的总和s。但是s的组是数字中这些位置的设置位数,因此,经过>>56运算后,我们得到了原始数字中设置位数的总数。只是(int32)类型转换为较小尺寸的数据类型(实际上足以存储这个数字。

注:某位被设置意味着该位等于 1。