The implementation counts 248-bit chunks using existing kernel arithmetic and uses a native C++ limb scan when compiled. These benchmarks are standalone artifacts, outside the repository and CI.
After building the PR's Lean checkout with its documented build command:
python3 run.py --repo /path/to/lean4 --out /tmp/popcount-kernel --kind kernel
python3 run.py --repo /path/to/lean4 --out /tmp/popcount-native --kind native
The runner reserves an affinity CPU, uses Lean's test harness, records host load and all completed samples, and removes its temporary benchmark source. Native timings execute a C-compiled binary; interpreter execution is disabled.
Median microseconds for dense inputs:
| Bits | Kernel: 64-bit chunks | Kernel: Nat.popcount | Compiled: 64-bit chunks | Compiled: Nat.popcount |
|---|---|---|---|---|
| 64 | 46.72 | 23.44 | 0.489 | 0.059 |
| 256 | 89.30 | 34.66 | 1.947 | 0.061 |
| 4096 | 959.89 | 199.79 | 33.280 | 0.125 |
| 65536 | 16275.56 | 3296.10 | 932.516 | 1.225 |
The 64-bit word arithmetic is Bhavik Mehta's raw-Nat popc64K from PrimeCert #156. The reference extracts and masks each 64-bit chunk from a natural number. This measures that chunk-counting implementation, not the complete PrimeCert sieve or a primality proof.
The same kernel run compares the selected 248-bit loop with otherwise identical 64- and 128-bit loops:
| Bits | 64-bit loop | 128-bit loop | 248-bit loop |
|---|---|---|---|
| 64 | 28.72 | 27.65 | 23.44 |
| 256 | 63.63 | 42.54 | 34.66 |
| 4096 | 709.29 | 395.97 | 199.79 |
| 65536 | 12699.12 | 6861.82 | 3296.10 |
All three use the same byte-counting operations. 248 bits is the largest multiple of eight whose bit count fits in one byte. The selected public function and the 248-bit prototype have identical reduction bodies.
Kernel samples time a fresh Kernel.check of an equality application. Literal input construction and expected-result calculation occur outside the timer. Every case rejects a wrong-result control before timing. Four blocks alternate adjacent AB/BA arms. current occurs once per comparison pair, so its reported median includes all 12 completed samples per input. Compiled samples time 100 checked calls per row; divide nanoseconds by 100 for per-call times. Sparse cases and a scalar 32-bit native case are included in the raw data. Host-specific timings include fixed checker/call overhead. Chunk extraction and structural-fuel reduction repeatedly copy big naturals; the kernel implementation does not have the native limb scan's linear complexity.
kernel-samples.csv and native-samples.csv contain every completed final-run sample; their matching records include the commands and host context. exploratory-samples.csv and exploratory-log.txt retain an earlier unpinned experiment with missing noncomputable annotations and its diagnostics; the tables above use only the successful final runs.
The division-* files retain the complete earlier run using division/modulo chunk extraction. The primary kernel files and tables use masks and shifts. Native code is identical in both versions; its completed run is retained without repetition.