A CPU benchmark suite that shows its work.
Apple M4 Pro · 10P + 4E · macOS 26.6
Six workloads, each measuring something the others cannot see. A CPU that is fast at all six is fast; one that is fast at a single one is fast at that one thing. The per-workload numbers are the actual result — the score is a convenience that throws away the shape.
| Workload | 1 thread | 14 threads | Scaling | Variation |
|---|---|---|---|---|
| Dhrystone 2.1 Dhry/s | 68.0M | 680M | 10.0× | 1.9% acceptable |
| SGEMM 256³ GFLOP/s | 19.1 | 186 | 9.7× | 2.8% acceptable |
| SHA-256 MiB/s | 422 | 3.75k | 8.9× | 2.7% acceptable |
| Sort 1Mi u64 Melem/s | 102 | 1.06k | 10.4× | 2.2% acceptable |
| STREAM Triad GiB/s | 110 | 214 | 1.9× | 1.1% acceptable |
| Memory Latency ns | 118 | — | — | 0.9% stable |
Variation is the coefficient of variation across 9 samples, after discarding 3 warmup rounds and rejecting outliers by median absolute deviation. Under 1% is stable; under 3% is usable; above that, the measurement is describing the room rather than the CPU.
Speedup from one thread to 14. Compute-bound workloads approach the core count; STREAM does not, and that is the interesting result — a single core already saturates most of this machine's memory controller, so there is little headroom left for the others.
Each workload's single-thread result as a multiple of the ThreadStone Reference Core v1 — a nominal 3.0 GHz out-of-order core with 256-bit SIMD and one DDR4-3200 channel. It is a published definition, not a machine anyone owns: a reference derived from the author's own hardware would score exactly 1000 and make every other machine look like a deviation from it.
One dependent load at a time, over working sets from 4 KiB to 256 MiB. Nothing can hide the miss, so each plateau is a cache level and each step is a boundary. This is the shape of the machine's memory system.
| Working set | Latency (ns) |
|---|---|
| 4 KiB | 0.9 |
| 8 KiB | 1.0 |
| 16 KiB | 1.0 |
| 32 KiB | 1.0 |
| 64 KiB | 0.9 |
| 128 KiB | 0.9 |
| 256 KiB | 6.0 |
| 512 KiB | 6.4 |
| 1 MiB | 6.2 |
| 2 MiB | 6.2 |
| 4 MiB | 7.8 |
| 8 MiB | 8.1 |
| 16 MiB | 15.6 |
| 32 MiB | 51.1 |
| 64 MiB | 98.4 |
| 128 MiB | 111.1 |
| 256 MiB | 119.2 |
Each covers a dimension of CPU performance the others are blind to.
The 1984 integer benchmark, ported to Rust and verified against the reference implementation's published final state. Small integers, dense branching, procedure calls, and 30-byte string copies — a working set that never leaves L1.
Dense f64 matrix multiply, cache-blocked so three 256×256 matrices sit in L2. Loop order is i-k-j, which makes the inner loop a contiguous AXPY that vectorises into FMAs instead of stalling on a dot-product accumulator.
The portable software path, no hardware SHA instructions — those are an order of magnitude faster and would measure the presence of one instruction rather than integer throughput. Verified against the NIST vectors.
The most realistic workload here. Unpredictable branches at every comparison and a recursive access pattern no prefetcher models — what ordinary application code actually does to a CPU.
McCalpin's Triad over three 64 MiB arrays, partitioned across threads so the footprint stays constant at every thread count. Counts 24 bytes per element, following STREAM's convention of ignoring read-for-ownership traffic.
A dependent-load pointer chase around one random Hamiltonian cycle: each step's address is the previous step's value, so nothing can hide the miss. Measured single-threaded only — splitting the buffer across threads would fit each slice in cache and report an LLC hit as DRAM latency.
Every design decision follows from one idea: a benchmark number is a claim, and a claim nobody can check is worthless.
A work-stealing pool hands out samples as slots free up, so early threads run against an idle machine and late ones against a loaded one. ThreadStone releases every thread from a barrier, so the window is exactly “time for all N threads, having started at the same instant.”
A fixed count that fills 300 ms on a laptop fills 3 ms on a server — close enough to the clock's granularity to be noise. Counts are discovered at run time, with every thread active, because a count tuned on an idle machine overshoots once memory is contended.
Allocation and page-faulting happen before the clock starts. Threads are spawned once for the whole workload, not once per sample.
Median rather than mean, because benchmark noise is one-sided — only ever making a sample slower. Outliers are rejected by median absolute deviation and counted, and each result states whether it should be believed.
CPU topology, cache sizes, OS, compiler version, target triple, optimisation flags, and the measured resolution of the clock itself. “2,300 Dhrystones/sec” is unfalsifiable; the same number with its machine attached is a claim you can refute.
Memory latency is single-thread only. Splitting a 256 MiB chase buffer across 14 threads would give each a slice that fits in last-level cache, so the “multi-threaded latency” would be an LLC hit time — several times better than reality, and a straightforward lie about the machine.
Full methodology, including the reference values, the statistics, and a known-limitations section.
git clone https://github.com/romankhadka/ThreadStone
cd threadstone
cargo install --path threadstone-cli --locked
threadstone run # the full suite, both passes
threadstone sweep # map your cache hierarchy
threadstone run --out mine.json # save the full document
threadstone compare theirs.json mine.json
Requires Rust 1.75 or newer. No C toolchain; three third-party crates in
the binary. --locked matters: a benchmark built against different
dependency versions is not the same benchmark, so Cargo.lock is
committed and every install measures with the same code.