Skip to content

popcount

CI coverage Go Reference

Fast population count (total set bits, a.k.a. Hamming weight) over a []byte, in pure Go (CGO_ENABLED=0, stable Go, no GOEXPERIMENT). Repository →

API

n := popcount.Count(data) // total number of 1 bits across all bytes of data

Count(data []byte) int returns Σ bits.OnesCount8(b) over every byte. It allocates nothing and is safe for concurrent use on read-only slices. The hot loop is assembly generated by go-asmgen:

arch kernel
amd64 4-way-unrolled hardware POPCNTQ; AVX2 VPSHUFB/Harley-Seal fallback
arm64 NEON VCNT + VUADDLV horizontal sum, 64 B/iter
loong64 LSX VPCNTV per-64-bit-lane popcount, 16 B/iter
ppc64le VSX VPOPCNTD per-64-bit-doubleword popcount, 16 B/iter
s390x vector-facility VPOPCT per-byte + VSUMB/VSUMQF reduction, 16 B/iter (big-endian)
others (incl. riscv64) portable math/bits.OnesCount64 word loop

Six architectures carry a native SIMD kernel: amd64, arm64, loong64, ppc64le and s390x (the ppc64le VSX path is the POWER8 baseline, s390x is the z13 vector facility, big-endian). riscv64 stays scalar (see below). loong64 and ppc64le are now measured on real silicon (GCC Compile Farm, 2026-06-26): the loong64 LSX VPCNTV kernel runs ~6.1× the scalar baseline on a Loongson 3A5000, and the ppc64le VSX VPOPCNTD kernel ~2.9× on real POWER9 — both supersede the earlier llvm-mca cycle-model estimates. s390x is now measured on real IBM z15 (VXE2) (2026-07-03, -count=6): the VPOPCT + VSUMB/VSUMQF kernel runs ~6.5× the scalar baseline and beats the barakmich SWAR reference — a clear vector win (unlike ppc64le, which barely edges strong scalar HW popcount).

The honest result: it's size-dependent

A scalar OnesCount64 loop already lowers to one POPCNTQ/CNT per word — roughly eight bytes per cycle. So for large, out-of-cache buffers the problem is memory-bandwidth-bound: nothing, SIMD included, moves bytes faster than the memory subsystem delivers them, and the wide kernel only ties a good scalar loop. The win is real for in-cache / medium buffers, where the problem is compute-bound. We therefore measure across sizes rather than claiming a flat speedup.

The decisive amd64 detail: when the CPU has hardware POPCNT, a 4-way-unrolled POPCNTQ loop beats Mula's AVX2 VPSHUFB/Harley-Seal kernel. POPCNTQ is 1/cycle but has a false output-dependency on several Intel parts, so four independent destination registers keep four in flight. AVX2 nibble-lookup only wins where POPCNT is absent, so it ships as the POPCNT-less fallback.

Performance

Throughput in GB/s (best-of-6), across the cache hierarchy. Indicative figures from an emulated Haswell (QEMU/TCG — treat the relative ordering as the signal; native CI is authoritative):

buffer scalar OnesCount64 barakmich Count (ours) regime
1 KiB 0.63 5.16 5.68 in-cache (L1)
64 KiB 0.66 6.71 7.68 in-cache (L2)
1 MiB 0.67 6.34 7.71 L2/L3 boundary
16 MiB 0.66 5.97 7.00 out-of-cache (RAM)

Count beats the scalar loop ~9–12× and edges out barakmich/go-popcount ~1.1–1.2× at every size on this host. Throughput rises to 1 MiB (compute-bound, SIMD wins) then falls back at 16 MiB as the buffer leaves cache and the kernel becomes memory-bandwidth-bound — exactly the expected size-dependent shape. The AVX2 kernel models at ~3.0 cyc RThroughput (Haswell) / ~2.0 (Zen3) per Harley-Seal reduction, still beaten by hardware POPCNTQ on POPCNT-capable cores.

riscv64

There is no SIMD kernel on riscv64. The base RVV extension has no per-element byte popcount, and Go's assembler exposes only vcpop.m (a mask popcount), not Zvbb's vcpop.v. riscv64 uses the portable OnesCount64 word loop. If/when Go's assembler gains vcpop.v, a kernel can be added without an API change.

Coverage

100% of the Go code on every architecture (native amd64 + arm64; riscv64 + loong64 + ppc64le + s390x under QEMU). All three amd64 dispatch branches (hardware POPCNT, AVX2 fallback, scalar) are exercised on the native amd64 runner by toggling the feature flags. The .s kernels are validated by the differential force tests against the scalar reference plus the fuzz target (the VSX and big-endian vector-facility kernels under qemu-user). BSD-3-Clause.