Most of what you believe about the chip in your MacBook is folklore:
numbers copied from an M1 reverse-engineering blog, review-site figures
measured at a level sysctl doesn't distinguish, x86 intuitions that
never ported, and vendor documentation that does not exist. This
repository is the antidote: a fact ledger for the Apple M2 Max in
which every claim is verified by an experiment built to kill it — and
which you can re-run, in minutes, on your own machine.
git clone https://github.com/bjornpagen/bumblebench && cd bumblebench
bash scripts/verify-all.sh # re-verifies the whole ledger, stamped with YOUR OS buildNo dependencies. Every number traceable to source code and to the disassembly it compiled to — a number you can't connect to the machine code is a rumor.
Every fact is also drawn. The atlas is a library of 39 measured plates — per-repetition scatter under a bold min-line, the fact's own tolerance band shaded onto the figure, regimes labeled, and a provenance stamp taken from the sweep data itself. Three to start:
The memory hierarchy, one dependent load at a time — and the SLC tier that isn't there:
The branch predictor memorizes your benchmark: below ~8K outcomes a "random" pattern is nearly free, past ~16K every branch is a coin flip. Most microbenchmarks live on the left side of this cliff and don't know it:
The out-of-order window, resource by resource: two DRAM misses stay overlapped until the filler between them exhausts the scheduler (~140), the load queue (~136), integer rename (~340), or the ROB (~590) — and nop fillers bind at none of them:
Every plate re-renders from committed sweep data with
scripts/atlas.sh --render-only, or from fresh measurements on your
machine with scripts/atlas.sh.
- Your Mac has two cache-line sizes, and
sysctlreports the wrong one for the L1D. The L1D manages 64-byte lines behind a memory system that moves 128 bytes; both published camps were right at different levels. (m2max.cache.l1d-line-64) - The famous "~630-entry ROB" is ~590 — and it is almost never your
bottleneck. The load queue (~136), the GPR rename window (~340),
and the issue queue (~115–140) all bind first. Bonus: the classic
nop-filler ROB microbenchmark measures nothing — nops
retire-coalesce. (
m2max.core.rob-sizeand siblings) - There is no SLC latency tier for the CPU. Dependent loads see a
monotonic ramp from L2 to DRAM — no 48 MB plateau — and naive
"SLC latency" sweeps are confounded by a coincidence: the L2 TLB's
translation reach is also 48 MB. (
m2max.cache.slc-behavior) - Page walks cost ~7 cycles here, not hundreds. The x86 horror
stories don't port: a DTLB miss that hits the L2 TLB is ≈7 cycles,
and even a full L2-TLB miss with resident tables is ≈35.
(
m2max.cache.tlb-miss-cost) - "L1 is 3–4 cycles" is two facts wearing one number: 3 cycles
simple addressing, 4 cycles scaled-index. Nobody had separated them.
(
m2max.cache.l1d-load-latency) - A branch mispredict costs 15.5 cycles — except when it costs 28.
The published "14–20" range is a staircase of in-flight state, and
each stair is measurable. (
m2max.core.pipeline-depth-mispredict) - E-cores at background QoS run their full 2.4 GHz. The "~1 GHz
background floor" belief is dead; E-cores are narrow, not slow —
their L1 is the same cycle count as the P-core's.
(
m2max.ecore.alu-clock-ratio) - An fsync can leave your P-core at 0.43 GHz — or leave you on an
E-core entirely. Both halves of the previously published story
(a 1.05–1.46 GHz floor, on one P core, always) were artifacts of the
measuring probe's own demand. (
m2max.clock.floor-e-migration) - 128-bit NEON beats the "8-wide" scalar core 2.65× on min/max —
not because vectors are wide, but because every flag-writing scalar
op is confined to 3 of the 6 integer ALUs. Deep out-of-order does
not substitute for SIMD on reductions.
(
m2max.core.flag-port-asymmetry) - Your benchmark harness is lying to you more than the chip is.
The predictor memorizes "random" branch data (4.7× swings on
identical instructions), LLVM silently rewrites kernels under test,
an unfenced timer stamp can misattribute a latency-bound span by
−99.6% — and macOS's own clock APIs are slide-proof while raw
counter reads are not. (
m2max.predict.tage-memorizes-benchmarks,m2max.timer.stamp-slide-law)
Every one of those sentences carries a fact id. Look it up in
FACTS.md — 66 entries across eleven domains — and each id
resolves to a full dossier in facts/.
A fact here is not a benchmark result; it is a falsifiable claim with
its number in the statement, and it carries, in order: the magnitude
and its measurement protocol; the regime where it holds (most facts
are regime tables — the single deepest lesson in this corpus is that a
regime-free performance claim is usually wrong somewhere); the tolerance
band outside which a re-run files a DRIFT; the falsifier — what
observation would kill it; the antagonist — the binary whose
--check mode attacks it, including a disassembly gate pinning the
measured loop's machine code; an append-only verification log stamped
with chip, macOS build, and toolchain; and graded provenance.
The canonical example is key-ahead prefetching
(m2max.probe.key-ahead-prefetch), one mechanism with four verdicts:
−21–27% on pressured hit streams, ≈0 on mixed streams, free
on miss streams, +6.4% loss inside a loop whose memory parallelism
is already saturated. Any document that gives you one number for it is
wrong three-quarters of the time.
Statuses are a machine: VERIFIED (asserting check green, gated, stamped) · DRIFTED (tolerance violated — an open investigation, never a silent re-pin) · REFUTED (killed; the gravestone keeps the numbers) · IMPORTED (credible source, no local proof) · FOLKLORE (no credible source — prime prey). The only path upward is through an antagonist. Nothing is ever deleted.
- Antagonism. An experiment's job is refutation, not demonstration. Where a common wrong belief exists, the check asserts it fails as a negative control — a gate that cannot fail cannot protect. The suite has already caught its own authors: a polarity-inverted fact statement, an inverted CPU-cluster calibration, a runner that printed "all green" over a drifted run. The machinery wins arguments with the people who built it, which is the point.
- The regime law. Every fact states the conditions under which it holds — cache tier, hit/miss mix, isolation vs in-situ, clock state. The antagonist's first move is the regime boundary, because that is where published claims go to die.
- The disassembly is the measurand. Every antagonist's hot loop is
pinned by objdump gates (
scripts/asm-gates/). Compilers rewrite benchmarks: LLVM has auto-vectorized "scalar" baselines, reassociated "serial" dependency chains, and manufactured 10-cycle store-forward stalls out of innocent source in this very repo. Debug symbols stay on in release so the hot loops read with names. - Measurement is stamped and serialized. One machine-wide lock; spin,
never sleep, before a timed region; min-of-repetitions; every run
records chip, macOS build, rustc, and date, because the OS is part of
the machine — the commpage clock behavior and the DVFS floor are OS
facts a point release can move.
REPORT.mddiffs every run against the last; drift is a finding, on your machine too.
The full law — schema, status machine, discipline — is
docs/00-charter.md. The style owes its spine to
Casey Muratori: hand-rolled timing,
no framework, data sized against the real hierarchy, machine code read
and quoted in every write-up.
cargo run --release --bin tlb_geometry # one experiment, exploratory tables
cargo run --release --bin tlb_geometry -- --check # asserting mode: FACT lines, exit code
bash scripts/verify-all.sh # the whole ledger -> REPORT.md
objdump -d target/release/tlb_geometry | less # read the hot loops yourselfEach binary has a companion write-up in docs/ — the question, the
hypothesis, the measured tables, the relevant disassembly, and the
verdict.
| antagonist | the question | the answer |
|---|---|---|
ooo_window |
how big are the OoO windows, really? | ROB ~590 µops; load queue ~136; GPR rename ~340; the smallest window binds first, and it is never the ROB |
tlb_geometry |
what does address translation cost? | DTLB ≈256 entries, L2 TLB ≈3072 (48 MB reach); misses ≈7/≈35 cy — walks are cheap here |
cache_latency |
the true latency ladder? | L1 3/4 cy by addressing mode; L2 19 cy with ~6 MB clean reach; no SLC tier; DRAM ~122 ns direct (~101 memory + ~10 walk) |
mispredict_depth |
what does a mispredict cost, alone? | 15.5 cy isolated, linear in rate; up to ~28 with in-flight state |
multicore_fabric |
how does the fabric scale? | 57–59 GB/s per P thread, ~122 GB/s plateau (per-cluster cap ~60); atomics 76 ns same-cluster / 220–300 cross; E-cores at full 2.4 GHz under background QoS |
floor_identity |
who runs at the fsync floor? | the thread migrates to E-cores ~30% of deep-band time AND P-cores floor at 0.43–0.88 GHz — the published story was the probe's own equilibrium |
mlp_batch_probes |
why didn't batching probes 37× help? | the map was L2-resident: the OoO core overlaps probes at batch 1; ~33 effective miss lanes at DRAM |
hash_probe_anatomy |
where do open-addressing inserts spend time? | ⅓ stores, ⅓ walk, ⅓ hash latency exposed by exit-branch mispredicts; hash-ahead recovers 55–65% — on branchy probes only |
sum_kernels |
does 2-lane NEON beat 6-wide scalar on sums? | 19.6 vs 11.8 rows/ns at L1; exact sums 1.9× at every cache tier — adds/adcs live on a 3-port triad |
neon_minmax |
why does the one NEON win win everywhere? | cmp+csel on 3 ports vs cmhi/bsl on 4 vector pipes → 2.65× at every tier; flag µops stranded behind misses halve MLP |
table_hoist_crossover |
where is the real hoist crossover? | the "+48 ns/row" was std::array::from_fn refusing to inline; real crossover ≈ 4–8 items |
prefetch_lead |
when does software prefetch land? | when dependents clog the ~115-entry issue queue or a phased batch idles memory: 4× at DRAM, pure loss L2-resident |
branchless_compaction |
is always-branchless right? | 1.00 cy/item flat at every selectivity; branchy never wins on random data and is 9× worse at 50% |
bounds_checks |
what do bounds checks cost in gathers? | ~zero as instructions; 1.6–1.7× structural at L1 (the check's basic block breaks the load interleave), ≤5% elsewhere |
identity_vs_gather |
the real bandwidth ceilings? | ceilings are latency × parallelism walls: 128 GB/s zva fill, ~110 NT copy, 16 GB/s random-gather line traffic |
l1d_line_and_aliasing |
64 or 128-byte lines? does the aliasing cliff exist? | both, at different levels; the feared set-aliasing cliff tops at 1.55× on real scans — the real 4–6× pathology is prefetch-tracker aliasing on power-of-two pitches |
timer_costs |
what do the instruments cost? | cntvct reads are 0.30 ns; isb fences cost more than the slide they prevent; unfenced stamps slide ≤ ~50 ns throughput-bound |
call_overhead_leaf |
the per-row toll of call-per-element? | the call is not the toll: SLP-merged accumulators manufacture a ~10-cycle un-renameable SIMD store→load round trip; bl/ret disables memory renaming |
shadow_composition |
do probe optimizations compose? | they are regime-dependent: window probing wins in cache and inverts 1.4–1.7× at DRAM; pairings must be measured jointly |
flush_shape |
what does a batched phase's overhead cost? | 9–30 ns per pass — 20× below the assumption an entire lever class was built on |
dedup_floor |
what does runtime key-arity cost? | 1.2–2.1× by regime; const-generic monomorphs get compiler fusion back for free |
simd_probe |
can a NEON sweep beat the scalar probe walk? | 3.5 ns flat in isolation — and it still loses in situ, because it touches key lines on misses the tag-gated walk never loads |
fsync_dvfs |
what does an fsync leave behind? | a demand-recovered clock floor, an E-core wake lottery after sleeps, and a probe-equilibrium trap that took a second antagonist to see through |
residency_budget |
what does co-residency displace? | hit streams pay first (+21% at 2–4 MB of streaming co-tenant); key-ahead prfm recovers 21–29% — on hit streams only |
probe_residual |
why do "resident" structures miss in situ? | residency is a property of phase interleaving, not footprint; full prefetch coverage buys the displaced cost back |
attribution_error |
how wrong can per-op attribution be? | a mis-counted denominator reproduces a 3× error class; latency-bound spans misattribute up to −99.6% with raw stamps |
(Plus follow-up instruments — hash_probe_drift, hash_probe_followup,
shadow_composition_followup, dedup_floor_followup — built to chase
specific anomalies; see their parent write-ups.)
All facts are pinned on one machine and stamped per run: Apple M2 Max
(Mac14,5), macOS (build recorded in every report), rustc per
rust-toolchain. That specificity is the point — "Apple Silicon"
generalizations are exactly the folklore this ledger exists to retire.
If you run it on an M1, M3, or M4, the drifts you see are not noise:
they are the cross-generation deltas nobody has published. Open an
issue with your REPORT.md.
0BSD — use anything, no attribution required.