
本文详解 getproccount 函数如何通过系统调用获取 cpu 亲和性掩码,并利用经典位操作(brian kernighan 变体 + 并行位计数)在常数时间内统计掩码中置位比特数,从而准确推导物理/逻辑核心数量。
本文详解 getproccount 函数如何通过系统调用获取 cpu 亲和性掩码,并利用经典位操作(brian kernighan 变体 + 并行位计数)在常数时间内统计掩码中置位比特数,从而准确推导物理/逻辑核心数量。
getproccount 是 Go 语言早期运行时(pre-1.4)中用于探测当前进程可调度 CPU 核心数量的关键函数。其核心逻辑分为三步:获取亲和性掩码 → 解析掩码数组 → 统计所有掩码字中置位(bit set)总数。最终返回值即为操作系统允许该进程使用的逻辑核心数;若系统调用失败或掩码全零,则保守返回 1。
关键步骤解析
1. 获取 CPU 亲和性掩码
int32 r = runtime·sched_getaffinity(0, sizeof(buf), buf);
该系统调用将当前进程的 CPU 亲和性掩码(affinity mask)写入 buf 数组。buf 类型为 uintptr[16],在 64 位系统中每个元素为 8 字节(64 位),因此最多可容纳 16 × 64 = 1024 个 CPU 核心的掩码信息。r 返回实际写入的字节数,故有效数组长度为 r / sizeof(uintptr)。
2. 对每个掩码字执行并行位计数(Population Count)
对 buf[i] 中的每个 uintptr 值 t,采用经典的 SWAR(SIMD Within A Register)位计数算法,仅用 3 行位运算即可在 O(1) 时间内算出其二进制表示中 1 的个数:
t = t - ((t >> 1) & 0x5555555555555555ULL); // Step 1: 每2位一组,计算组内1的个数(0~2) t = (t & 0x3333333333333333ULL) + ((t >> 2) & 0x3333333333333333ULL); // Step 2: 每4位一组,累加(0~4) cnt += (int32)((((t + (t >> 4)) & 0xF0F0F0F0F0F0F0FULL) * 0x101010101010101ULL) >> 56); // Step 3: 全局求和(0~64)
算法原理简述:
- Step 1:0x5555...(二进制 010101...)用于隔离奇偶位。t - (t>>1 & mask) 将每相邻 2 位视为一个“计数单元”,输出值即为该单元中 1 的数量(如 11 → 10₂ = 2)。
- Step 2:0x3333...(00110011...)提取每 4 位中的低 2 位与高 2 位,相加后得到每 4 位中 1 的总数。
- Step 3:先用 t + (t>>4) 将每 8 位内的前 4 位与后 4 位相加;& 0xF0F0... 提取每 8 位的高 4 位(即结果);再乘以 0x1010101...(二进制为 1 间隔 7 位),实现“字节内广播求和”——最高字节自动累加所有字节的值;最后 >>56 提取最高字节,即为整个 64 位字中 1 的总数。
✅ 该算法无需分支、无查表、全流水,是编译器内置 __builtin_popcount 的底层实现逻辑之一,在无硬件 POPCNT 指令的老平台(如部分 ARM 或早期 x86)上尤为关键。
3. 边界处理与默认回退
return cnt ? cnt : 1;
若 sched_getaffinity 调用失败(r ≤ 0)或所有掩码字均为 0(cnt == 0),函数返回 1。这是一种安全降级策略:确保程序至少能在单核上运行,避免因探测失败导致不可用。
注意事项与工程启示
- 可移植性限制:此实现依赖 uintptr 为 64 位且系统支持 sched_getaffinity(Linux/FreeBSD),在 Windows 或旧内核上需适配。
- 逻辑核 ≠ 物理核:该函数返回的是操作系统可见的逻辑处理器数量(含超线程),非物理核心数。如需区分,需读取 /sys/devices/system/cpu/ 下的拓扑信息。
- 现代替代方案:Go 1.5+ 已弃用此手写位运算,转而使用 runtime.NumCPU(),其底层可能调用 sysconf(_SC_NPROCESSORS_ONLN) 或直接读取 /proc/sys/kernel/osrelease,更简洁且兼容性更强。
- 性能提示:尽管位运算极快,但 sched_getaffinity 是系统调用,存在上下文切换开销。生产环境应缓存结果,避免高频调用。
掌握此类底层位操作,不仅有助于理解运行时设计哲学,更是构建高性能系统软件(如调度器、内存分配器)的必备基础能力。










