popcount¶
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¶
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.