From a9686b9118a6529db6c3782d35d5d48f74e3b4b9 Mon Sep 17 00:00:00 2001
From: helly25 <6420169+helly25@users.noreply.github.com>
Date: Sat, 11 Jul 2026 21:28:40 +0100
Subject: [PATCH 1/9] mbo/hash: authoritative M5 Pro (clang-22) + Zen5 (gcc-15)
measurements; published README
First real per-machine measurement data under the new scheme: two authoritative
bundles (clean main 1417af5e) - Apple M5 Pro / clang-22 and AMD Ryzen 9 9950X /
gcc-15 - committed via Git LFS, and the hash README performance section rendered
from them by `publish` (one labeled block per machine: charts + curated tables).
Dropped the old hand-maintained single-machine tables and the CI cross-platform
section (both superseded by the two real machine blocks) and generalized the
prose. `verify` confirms the committed charts match the bundle data.
---
mbo/hash/README.md | 277 ++++++++++--------
...n-9-9950x-16-core-processor_gcc-15_128.svg | 40 +++
...en-9-9950x-16-core-processor_gcc-15_64.svg | 59 ++++
.../macos-arm64-apple-m5-pro_clang-22_128.svg | 40 +++
.../macos-arm64-apple-m5-pro_clang-22_64.svg | 59 ++++
...or_10c_gcc-15_1417af5e_20260711_203603.tgz | 3 +
..._18c_clang-22_1417af5e_20260711_211526.tgz | 3 +
7 files changed, 353 insertions(+), 128 deletions(-)
create mode 100644 mbo/hash/measurements/charts/linux-x86-64-amd-ryzen-9-9950x-16-core-processor_gcc-15_128.svg
create mode 100644 mbo/hash/measurements/charts/linux-x86-64-amd-ryzen-9-9950x-16-core-processor_gcc-15_64.svg
create mode 100644 mbo/hash/measurements/charts/macos-arm64-apple-m5-pro_clang-22_128.svg
create mode 100644 mbo/hash/measurements/charts/macos-arm64-apple-m5-pro_clang-22_64.svg
create mode 100644 mbo/hash/measurements/data/linux-x86-64-amd-ryzen-9-9950x-16-core-processor_10c_gcc-15_1417af5e_20260711_203603.tgz
create mode 100644 mbo/hash/measurements/data/macos-arm64-apple-m5-pro_18c_clang-22_1417af5e_20260711_211526.tgz
diff --git a/mbo/hash/README.md b/mbo/hash/README.md
index aee636b..408e516 100644
--- a/mbo/hash/README.md
+++ b/mbo/hash/README.md
@@ -213,148 +213,169 @@ The two frameworks compose rather than compete - pick by contract:
## Performance
-Measured and rendered by `mbo/hash/measurements/hash_benchmark_report.py`
-(Apple Silicon arm64, Apple clang, `-c opt`; see
-[mbo/hash/measurements/](measurements/README.md)). Numbers are the **mean of the
-3 fastest of 9 repetitions** with random interleaving and warmup: on a shared
-machine the fast tail approximates the uncontended cost (contention only ever
-adds time), and averaging the best few rejects a single-sample fluke while
-staying far more reproducible than the median at sub-nanosecond scale. Bold =
-fastest per length. Lengths straddle the dispatch-tier and SSO boundaries (7/8
-the fully-unrolled `<= 8` path, 15/16 the `<= 16` path and libstdc++ SSO cap, 22
-the libc++ SSO cap, 47/48 and 63/64 the short-chain steps). The tool's full mode
-sweeps a denser exponential curve.
-
-Full-sweep curves (log-log axes, `run_measurements.py`; the tables below are the
-dense README subset). Everything between the markers is regenerated per machine
-by `hash_benchmark_report.py publish` from the committed data bundles:
+Measured with the tooling in [`mbo/hash/measurements/`](measurements/README.md)
+(`-c opt`). Each block below is one machine and compiler, generated from a
+committed data bundle. Numbers are the mean of the 3 fastest of 9 runs (with
+interleaving and warmup): on a shared machine the fastest runs are the least
+contended, and averaging a few is steadier than the median at these
+sub-nanosecond sizes. Bold marks the fastest per row; the tables use a curated
+set of lengths (straddling the dispatch-tier and SSO boundaries), the log-log
+charts a denser one.
+
+Everything between the markers is generated per machine by `publish` from the
+committed bundles - regenerate it, don't hand-edit:
+
-
+### Apple M5 Pro · arm64 · 18-core · clang-22 · 1417af5e
-
+
+
-### 64-bit one-shot throughput (ns/op, mean of the 3 fastest of 9 reps; lower is better)
+
+
+#### 64-bit one-shot throughput (ns/op, mean of the 3 fastest of 9 reps; lower is better)
| Length | mumbo | rapidhash | xxh3 | xxh64 | murmur3 | siphash24 | fnv1a | dumbo |
| -----: | --------- | --------- | ----- | ----- | ------- | --------- | -------- | -------- |
-| 1B | 2.28 | 1.89 | 1.80 | 1.97 | 2.73 | 6.61 | **0.50** | 1.62 |
-| 3B | 2.35 | 1.88 | 1.79 | 2.63 | 2.95 | 6.66 | **1.02** | 2.58 |
-| 7B | 2.48 | 1.74 | 1.64 | 2.86 | 2.96 | 6.96 | 2.55 | **1.51** |
-| 8B | 2.04 | 1.69 | 1.59 | 2.28 | 3.03 | 9.17 | 2.89 | **1.57** |
-| 11B | 2.00 | **1.70** | 1.73 | 3.23 | 3.50 | 9.33 | 4.14 | 3.02 |
-| 15B | 2.02 | **1.72** | 1.72 | 3.69 | 3.60 | 9.36 | 6.01 | 1.91 |
-| 16B | 2.01 | **1.73** | 1.74 | 2.72 | 3.96 | 12.10 | 6.46 | 1.90 |
-| 19B | 2.44 | **1.97** | 2.39 | 3.85 | 4.44 | 12.30 | 7.62 | 3.53 |
-| 22B | 2.44 | **2.02** | 2.39 | 4.05 | 4.56 | 12.29 | 9.03 | 2.46 |
-| 27B | 2.44 | **2.03** | 2.39 | 4.68 | 5.15 | 15.35 | 11.68 | 3.62 |
-| 32B | 2.44 | **1.98** | 2.38 | 5.06 | 5.43 | 18.46 | 15.40 | 3.11 |
-| 47B | 2.81 | **2.42** | 3.79 | 8.52 | 6.45 | 21.82 | 27.43 | 4.27 |
-| 48B | 2.83 | **2.44** | 3.79 | 6.72 | 6.95 | 25.17 | 28.46 | 4.21 |
-| 63B | 3.36 | **2.85** | 3.79 | 10.35 | 8.03 | 28.75 | 40.24 | 5.55 |
-| 64B | 3.32 | **2.84** | 3.79 | 6.69 | 8.82 | 32.04 | 41.06 | 5.54 |
-| 256B | 8.69 | **7.54** | 26.58 | 16.12 | 33.72 | 120.9 | 288.3 | 31.20 |
-| 1Ki | 23.75 | **22.47** | 58.46 | 55.28 | 164.5 | 476.9 | 1327 | 194.4 |
-| 4Ki | **82.39** | 83.90 | 175.2 | 228.4 | 687.6 | 1908 | 5548 | 871.5 |
-
-### 128-bit one-shot throughput (ns/op, mean of the 3 fastest of 9 reps; native-128 algorithms only)
+| 1B | 2.17 | 1.97 | 2.43 | 2.34 | 2.80 | 6.43 | **0.49** | 1.57 |
+| 3B | 2.30 | 1.96 | 2.42 | 3.28 | 3.00 | 6.60 | **1.03** | 2.09 |
+| 5B | 2.34 | 1.74 | 2.46 | 2.41 | 3.02 | 6.82 | 1.80 | **1.48** |
+| 7B | 2.32 | 1.74 | 2.50 | 3.56 | 3.03 | 6.90 | 2.54 | **1.48** |
+| 8B | 2.09 | 1.68 | 2.42 | 2.69 | 3.04 | 9.13 | 2.99 | **1.47** |
+| 11B | 2.03 | **1.69** | 2.39 | 3.46 | 3.54 | 9.27 | 4.07 | 2.44 |
+| 15B | 2.00 | **1.70** | 2.36 | 3.85 | 3.62 | 9.26 | 6.14 | 1.89 |
+| 16B | 2.01 | **1.71** | 2.31 | 3.17 | 3.87 | 12.08 | 6.46 | 1.90 |
+| 19B | 2.44 | **1.98** | 2.89 | 4.24 | 4.46 | 12.26 | 7.59 | 3.00 |
+| 22B | 2.44 | **1.94** | 2.89 | 3.95 | 4.61 | 12.26 | 8.99 | 2.48 |
+| 27B | 2.44 | **1.95** | 2.89 | 5.09 | 5.10 | 15.27 | 11.69 | 3.54 |
+| 32B | 2.44 | **1.99** | 2.89 | 5.07 | 5.18 | 18.43 | 15.39 | 3.00 |
+| 38B | 2.88 | **2.34** | 4.23 | 6.82 | 5.84 | 18.41 | 19.35 | 3.62 |
+| 47B | 2.87 | **2.35** | 4.22 | 8.52 | 6.60 | 21.79 | 27.44 | 4.26 |
+| 48B | 2.88 | **2.34** | 4.23 | 6.72 | 6.72 | 25.24 | 28.93 | 4.22 |
+| 63B | 3.37 | **2.84** | 4.22 | 10.31 | 8.14 | 28.67 | 40.26 | 5.49 |
+| 64B | 3.37 | **2.89** | 4.23 | 6.68 | 8.40 | 31.95 | 41.07 | 5.49 |
+| 127B | 6.04 | **4.85** | 6.92 | 13.70 | 15.14 | 57.83 | 117.3 | 12.41 |
+| 128B | 6.55 | **4.84** | 6.91 | 9.66 | 15.55 | 61.64 | 118.4 | 12.38 |
+| 256B | 8.69 | **7.44** | 26.46 | 16.12 | 32.96 | 119.5 | 287.7 | 31.00 |
+| 1Ki | 23.73 | **22.50** | 58.92 | 55.30 | 161.1 | 479.9 | 1271 | 193.5 |
+| 4Ki | **82.37** | 83.87 | 176.3 | 222.9 | 692.6 | 1915 | 5503 | 871.8 |
+
+#### 128-bit one-shot throughput (ns/op, mean of the 3 fastest of 9 reps; native-128 algorithms only)
+
+| Length | jumbo | xxh3 | murmur3 |
+| -----: | --------- | -------- | -------- |
+| 1B | 3.12 | 2.88 | **2.81** |
+| 3B | 3.23 | **2.92** | 2.99 |
+| 5B | 3.08 | **2.47** | 3.02 |
+| 7B | 3.07 | **2.48** | 3.04 |
+| 8B | 2.96 | **2.50** | 3.01 |
+| 11B | 2.96 | **2.96** | 3.54 |
+| 15B | **2.96** | 2.96 | 3.64 |
+| 16B | **2.96** | 2.96 | 3.86 |
+| 19B | **3.86** | 4.05 | 4.47 |
+| 22B | **3.86** | 3.96 | 4.57 |
+| 27B | **3.86** | 3.94 | 5.11 |
+| 32B | **3.86** | 3.98 | 5.23 |
+| 38B | **4.45** | 5.30 | 5.86 |
+| 47B | **4.44** | 5.31 | 6.68 |
+| 48B | **4.43** | 5.31 | 6.81 |
+| 63B | **5.29** | 5.31 | 8.17 |
+| 64B | 6.21 | **5.31** | 8.55 |
+| 127B | 8.77 | **8.33** | 15.27 |
+| 128B | **7.48** | 8.32 | 15.61 |
+| 256B | **10.55** | 28.10 | 32.83 |
+| 1Ki | **31.69** | 60.54 | 162.2 |
+| 4Ki | **120.4** | 177.7 | 700.6 |
+
+#### Mixed-length latency (ns/hash, mean of the 3 fastest of 9 reps; lower is better)
+
+| max len | mumbo | rapidhash | xxh3 | xxh64 | murmur3 | siphash24 | fnv1a | dumbo |
+| ------: | --------- | --------- | ----- | ----- | ------- | --------- | ----- | ----- |
+| 16 | **9.88** | 10.68 | 11.52 | 13.98 | 15.48 | 18.99 | 14.70 | 12.67 |
+| 64 | **12.37** | 12.64 | 12.74 | 20.78 | 20.82 | 29.22 | 35.14 | 18.82 |
+| 1024 | 27.81 | **27.55** | 37.07 | 66.34 | 69.60 | 231.0 | 604.0 | 99.15 |
+
+### AMD Ryzen 9 9950X 16-Core Processor · x86_64 · 10-core · gcc-15 · 1417af5e
+
+
+
+
+
+
+#### 64-bit one-shot throughput (ns/op, mean of the 3 fastest of 9 reps; lower is better)
+
+| Length | mumbo | rapidhash | xxh3 | xxh64 | murmur3 | siphash24 | fnv1a | dumbo |
+| -----: | -------- | --------- | -------- | ----- | ------- | --------- | -------- | ----- |
+| 1B | 1.39 | 1.71 | 1.09 | 1.50 | 2.39 | 5.29 | **0.36** | 1.12 |
+| 3B | 1.48 | 1.65 | 1.09 | 2.10 | 2.85 | 6.05 | **0.73** | 1.57 |
+| 5B | 1.30 | 1.65 | **0.99** | 1.93 | 2.58 | 5.13 | 1.17 | 1.26 |
+| 7B | 1.30 | 1.65 | **0.99** | 2.60 | 2.58 | 5.13 | 1.56 | 1.26 |
+| 8B | 1.30 | 1.47 | **0.99** | 1.94 | 2.58 | 6.51 | 1.78 | 1.26 |
+| 11B | 1.30 | 1.47 | **0.79** | 3.01 | 3.40 | 7.70 | 2.56 | 1.90 |
+| 15B | 1.30 | 1.47 | **0.79** | 3.55 | 3.00 | 7.17 | 3.73 | 1.40 |
+| 16B | 1.30 | 1.47 | **0.79** | 2.36 | 2.77 | 8.04 | 4.04 | 1.40 |
+| 19B | 1.84 | 1.84 | **1.62** | 3.49 | 3.87 | 8.64 | 4.99 | 2.27 |
+| 22B | 1.84 | 1.84 | **1.62** | 3.65 | 3.55 | 8.23 | 6.07 | 1.78 |
+| 27B | 1.83 | 1.84 | **1.62** | 4.07 | 4.51 | 10.10 | 8.12 | 2.66 |
+| 32B | 1.84 | 1.84 | **1.62** | 4.22 | 3.68 | 11.26 | 10.21 | 2.25 |
+| 38B | 2.02 | **2.02** | 2.52 | 5.82 | 4.50 | 11.31 | 12.75 | 2.68 |
+| 47B | **2.02** | 2.02 | 2.52 | 7.09 | 4.94 | 12.93 | 18.11 | 3.28 |
+| 48B | 2.02 | **2.02** | 2.52 | 5.65 | 4.67 | 14.49 | 18.80 | 3.25 |
+| 63B | 2.30 | **2.19** | 2.52 | 8.55 | 5.79 | 16.20 | 26.28 | 4.28 |
+| 64B | 2.29 | **2.19** | 2.52 | 5.26 | 5.60 | 17.71 | 26.85 | 4.26 |
+| 127B | 4.21 | **3.87** | 4.37 | 11.15 | 9.54 | 29.13 | 67.24 | 9.18 |
+| 128B | 10.26 | **3.86** | 4.33 | 7.17 | 9.51 | 30.64 | 67.94 | 9.18 |
+| 256B | 11.97 | **5.87** | 54.80 | 11.15 | 18.20 | 56.54 | 163.5 | 21.82 |
+| 1Ki | 34.99 | **19.31** | 98.09 | 34.90 | 72.59 | 214.2 | 709.8 | 117.0 |
+| 4Ki | 133.6 | **72.78** | 319.8 | 130.8 | 294.2 | 835.5 | 2891 | 528.6 |
+
+#### 128-bit one-shot throughput (ns/op, mean of the 3 fastest of 9 reps; native-128 algorithms only)
| Length | jumbo | xxh3 | murmur3 |
| -----: | --------- | -------- | ------- |
-| 1B | 3.13 | **2.36** | 2.74 |
-| 3B | 3.30 | **2.32** | 2.99 |
-| 7B | 3.04 | **2.23** | 3.00 |
-| 8B | 2.96 | **2.23** | 3.05 |
-| 11B | 2.93 | **2.68** | 3.53 |
-| 15B | 2.93 | **2.67** | 3.61 |
-| 16B | 2.95 | **2.67** | 3.98 |
-| 19B | 3.81 | **3.48** | 4.58 |
-| 22B | 3.81 | **3.50** | 4.58 |
-| 27B | 3.82 | **3.46** | 5.04 |
-| 32B | 3.81 | **3.44** | 5.41 |
-| 47B | **4.44** | 4.99 | 6.43 |
-| 48B | **4.43** | 4.99 | 7.09 |
-| 63B | 5.28 | **4.99** | 8.08 |
-| 64B | 6.17 | **4.99** | 8.70 |
-| 256B | **10.56** | 28.00 | 33.65 |
-| 1Ki | **31.65** | 60.63 | 164.2 |
-| 4Ki | **123.3** | 177.7 | 698.2 |
-
-### Mixed-length latency (ns/hash, mean of the 3 fastest of 9 reps; lower is better)
-
-Each hash result selects the next key, serializing the dependency chain and
-defeating the size-dispatch branch predictor - the cost profile a hash table
-actually pays (as opposed to the hot, size-predictable throughput loop above).
-
-| max len | mumbo | rapidhash | xxh3 | xxh64 | murmur3 | siphash24 | fnv1a | dumbo |
-| ------: | --------- | --------- | ----- | -------- | ------- | --------- | ----- | ----- |
-| 16 | **10.14** | 10.38 | 11.67 | 13.69 | 14.86 | 18.54 | 13.11 | 11.42 |
-| 64 | 11.78 | 11.47 | 11.93 | **9.90** | 19.73 | 27.83 | 11.41 | 17.20 |
-| 1024 | **26.46** | 26.74 | 37.26 | 65.11 | 69.52 | 229.4 | 604.2 | 97.80 |
+| 1B | 2.38 | **1.46** | 2.60 |
+| 3B | 2.38 | **1.46** | 3.07 |
+| 5B | 2.23 | **1.25** | 2.84 |
+| 7B | 2.23 | **1.26** | 2.84 |
+| 8B | 2.21 | **1.25** | 2.84 |
+| 11B | 2.20 | **1.64** | 3.61 |
+| 15B | 2.21 | **1.65** | 3.19 |
+| 16B | 2.21 | **1.65** | 3.04 |
+| 19B | 2.78 | **2.24** | 4.06 |
+| 22B | 2.78 | **2.24** | 3.80 |
+| 27B | 2.78 | **2.24** | 4.66 |
+| 32B | 2.78 | **2.24** | 3.91 |
+| 38B | 3.46 | **3.15** | 4.70 |
+| 47B | 3.46 | **3.15** | 5.10 |
+| 48B | 3.46 | **3.15** | 4.87 |
+| 63B | 4.00 | **3.14** | 6.06 |
+| 64B | 4.68 | **3.14** | 5.86 |
+| 127B | 6.74 | **5.06** | 9.80 |
+| 128B | 5.76 | **5.02** | 9.81 |
+| 256B | **8.01** | 57.73 | 18.48 |
+| 1Ki | **22.50** | 100.8 | 72.88 |
+| 4Ki | **81.15** | 322.9 | 294.0 |
+
+#### Mixed-length latency (ns/hash, mean of the 3 fastest of 9 reps; lower is better)
+
+| max len | mumbo | rapidhash | xxh3 | xxh64 | murmur3 | siphash24 | fnv1a | dumbo |
+| ------: | ----- | --------- | ----- | ----- | ------- | --------- | -------- | ----- |
+| 16 | 4.29 | **4.12** | 4.29 | 7.18 | 6.68 | 8.48 | 6.88 | 5.78 |
+| 64 | 5.21 | 5.49 | 1.57 | 13.48 | 8.23 | 11.86 | **0.27** | 8.22 |
+| 1024 | 22.29 | **9.93** | 49.64 | 39.52 | 44.07 | 112.2 | 331.3 | 59.00 |
-Reading the results: `rapidhash` leads small keys, but after the if-ladder load
-path (see the design iterations) `mumbo` sits ~2.0 ns through 16 bytes -
-within ~0.3 ns of rapidhash across the inline-`std::string` range (2.01 ns
-at 16 B, 2.02 ns at the 15 B libstdc++ SSO cap). The remaining gap is the
-17-64 B short-chain tier (mumbo ~2.4-3.4 ns vs rapidhash ~2.0-2.9), the
-sequential 16-byte MUM chain; mumbo retakes the 4 KiB bulk (82.4 ns) and is the
-fastest strong algorithm in the latency chain at 16 B and the leader at 1 KiB,
-so the throughput deficit does not carry into the dependency-bound case. That
-small-key gap is mumbo's deliberate price: the two-multiply finalizer that earns
-the clean 188/188 in BOTH widths. For 128-bit, `xxh3` leads to 32 bytes but
-`jumbo` pulls decisively ahead from 47 bytes up (1.5-2.9x beyond 256 B) and is
-the only SMHasher3-clean native 128 on the rig. `fnv1a` wins the 1-3 B corner
-(no finalizer at all), and the redesigned `dumbo` takes 7-8 B and stays within
-~0.2 ns of rapidhash at 15-16 B (~1.9 ns) before falling off on larger keys
-(its single serial MUM accumulator, though far less steeply than the legacy
-hash: 31.2 ns vs 68.7 at 256 B). In the dependency-bound latency chain, dumbo's
-two-multiply finalizer costs it the tiny-key lead the finalizer-free legacy
-version used to hold (11.4 ns at 16 B vs mumbo's 10.1) - the same finalizer that
-lifts it from 40/188 to a clean 188/188. `siphash` pays its PRF security
-throughout.
-
-### Performance across platforms
-
-The CI benchmark job measures every push on two architectures (mean of 3
-repetitions; values `ubuntu-latest` x86_64 gcc / `macos-26` arm64 Apple
-clang, ns/op, from the PR #235 run). Shared runners are noisy - these numbers
-are for architecture/compiler _shape_ comparisons, not absolutes; entries
-marked `*` are gcc constant-folding artifacts on fixed-size lanes.
-
-Mixed-length latency:
-
-| max len | mumbo | rapidhash | xxh3 | xxh64 | murmur3 | siphash | fnv1a | dumbo |
-| ------: | ----------- | --------------- | ------------ | ----------- | ----------- | --------- | ----------- | ----------- |
-| 16 | 7.5 / 9.8 | 8.1 / 10.9 | 7.7 / 12.0 | 12.6 / 14.1 | 12.4 / 16.1 | 15 / 22 | 12.5 / 13.2 | 20.4 / 9.8 |
-| 64 | 9.3 / 11.2 | 10.1 / 11.4 | 3.6* / 12.6 | 25.4 / 21.4 | 15.7 / 20.1 | 22 / 35 | 0.9* / 38.1 | 69.9 / 26.7 |
-| 1024 | 41.4 / 29.2 | **18.9 / 27.4** | 111.5 / 44.2 | 120 / 74.6 | 105 / 78.4 | 216 / 282 | 581 / 570 | 837 / 374 |
-
-64-bit one-shot:
-
-| size | mumbo | rapidhash | xxh3 | xxh64 |
-| ----: | --------------- | --------------- | ------------- | ----------- |
-| 16B | 3.1 / 3.0 | 3.4 / **2.3** | **1.7** / 2.6 | 5.4 / 3.5 |
-| 256B | 23.2 / 12.5 | **13.6 / 9.5** | 121.8 / 40.4 | 33.6 / 19.5 |
-| 4 KiB | 275 / **103.0** | **183** / 102.6 | 749 / 210 | 445 / 308 |
-
-128-bit one-shot:
-
-| size | jumbo | xxh3 | murmur3 |
-| ----: | --------------- | ------------- | ----------- |
-| 16B | 9.3 / 4.4 | **3.2 / 3.7** | 9.0 / 8.9 |
-| 256B | **19.6 / 13.3** | 128.5 / 43.8 | 47.9 / 53.6 |
-| 4 KiB | **184 / 127** | 683 / 214 | 721 / 863 |
-
-Cross-platform reading: the mumbo/rapidhash near-tie holds on both
-architectures (rapidhash leads x86_64-gcc bulk; they tie on arm64), `jumbo`
-is the fastest 128-bit hash from 256 bytes up on both platforms, and the
-xxh3 mid-size dip plus the fnv1a/siphash profiles reproduce everywhere. (The
-`dumbo` row above is the PR #235 CI run of the legacy hash and predates the
-redesign; it refreshes on the next `main` CI benchmark - the single-rig
-64-bit table above already reflects the redesigned dumbo.)
+Reading the tables (exact numbers are per machine above; the pattern holds across
+arm64/clang and x86_64/gcc): `mumbo` and `rapidhash` are close on small keys.
+`mumbo` gives up a little on 17-64 byte keys - the cost of the extra finalizer
+that gets it a clean SMHasher3 pass - but wins the mixed-length latency test,
+which is closer to how a hash table actually uses a hash. For 128-bit output,
+`jumbo` is fastest from the mid sizes up. `fnv1a` is quickest on 1-3 byte keys,
+`dumbo` does well on tiny keys but falls off on large ones, and `siphash` is
+slower throughout - the price of being a keyed PRF.
## Quality: SMHasher3
diff --git a/mbo/hash/measurements/charts/linux-x86-64-amd-ryzen-9-9950x-16-core-processor_gcc-15_128.svg b/mbo/hash/measurements/charts/linux-x86-64-amd-ryzen-9-9950x-16-core-processor_gcc-15_128.svg
new file mode 100644
index 0000000..2f54b54
--- /dev/null
+++ b/mbo/hash/measurements/charts/linux-x86-64-amd-ryzen-9-9950x-16-core-processor_gcc-15_128.svg
@@ -0,0 +1,40 @@
+
diff --git a/mbo/hash/measurements/charts/linux-x86-64-amd-ryzen-9-9950x-16-core-processor_gcc-15_64.svg b/mbo/hash/measurements/charts/linux-x86-64-amd-ryzen-9-9950x-16-core-processor_gcc-15_64.svg
new file mode 100644
index 0000000..34768a0
--- /dev/null
+++ b/mbo/hash/measurements/charts/linux-x86-64-amd-ryzen-9-9950x-16-core-processor_gcc-15_64.svg
@@ -0,0 +1,59 @@
+
diff --git a/mbo/hash/measurements/charts/macos-arm64-apple-m5-pro_clang-22_128.svg b/mbo/hash/measurements/charts/macos-arm64-apple-m5-pro_clang-22_128.svg
new file mode 100644
index 0000000..d1478d1
--- /dev/null
+++ b/mbo/hash/measurements/charts/macos-arm64-apple-m5-pro_clang-22_128.svg
@@ -0,0 +1,40 @@
+
diff --git a/mbo/hash/measurements/charts/macos-arm64-apple-m5-pro_clang-22_64.svg b/mbo/hash/measurements/charts/macos-arm64-apple-m5-pro_clang-22_64.svg
new file mode 100644
index 0000000..6c95591
--- /dev/null
+++ b/mbo/hash/measurements/charts/macos-arm64-apple-m5-pro_clang-22_64.svg
@@ -0,0 +1,59 @@
+
diff --git a/mbo/hash/measurements/data/linux-x86-64-amd-ryzen-9-9950x-16-core-processor_10c_gcc-15_1417af5e_20260711_203603.tgz b/mbo/hash/measurements/data/linux-x86-64-amd-ryzen-9-9950x-16-core-processor_10c_gcc-15_1417af5e_20260711_203603.tgz
new file mode 100644
index 0000000..a8f8882
--- /dev/null
+++ b/mbo/hash/measurements/data/linux-x86-64-amd-ryzen-9-9950x-16-core-processor_10c_gcc-15_1417af5e_20260711_203603.tgz
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a2d037dade17c1434de088c6054eb7cad61e0cf33dfba845a9a9ab43378cd08f
+size 644644
diff --git a/mbo/hash/measurements/data/macos-arm64-apple-m5-pro_18c_clang-22_1417af5e_20260711_211526.tgz b/mbo/hash/measurements/data/macos-arm64-apple-m5-pro_18c_clang-22_1417af5e_20260711_211526.tgz
new file mode 100644
index 0000000..498a2a5
--- /dev/null
+++ b/mbo/hash/measurements/data/macos-arm64-apple-m5-pro_18c_clang-22_1417af5e_20260711_211526.tgz
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:233ad31a6aa613838eaf22ba239839b1997c1a11b6b86fc5bbfbc013d7b27922
+size 653076
From 43aef0b223b191529d7209c05ec71bad761148f1 Mon Sep 17 00:00:00 2001
From: helly25 <6420169+helly25@users.noreply.github.com>
Date: Sat, 11 Jul 2026 21:34:15 +0100
Subject: [PATCH 2/9] mbo/hash: markdownlint MD024 siblings_only for
per-machine README blocks
Each per-machine benchmark section repeats the same table subheadings
(64-bit / 128-bit / latency) under a different machine heading. Those are
not real duplicates, so scope MD024 to actual siblings.
---
.trunk/configs/.markdownlint.yaml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/.trunk/configs/.markdownlint.yaml b/.trunk/configs/.markdownlint.yaml
index 16aafc0..4440d71 100644
--- a/.trunk/configs/.markdownlint.yaml
+++ b/.trunk/configs/.markdownlint.yaml
@@ -11,3 +11,8 @@ whitespace: false
# CHANGELOG.md uses one top-level heading per version (matching the regex in
# .pre-commit/check_version.sh and tools/trigger_release.sh).
single-h1: false
+# The generated per-machine benchmark blocks repeat the same table subheadings
+# (64-bit / 128-bit / latency) under different machine headings, which is fine -
+# only flag duplicate headings that are actual siblings.
+MD024:
+ siblings_only: true
From f0c59578a4a98cf5c7fdc4a5ae4690524ca958d2 Mon Sep 17 00:00:00 2001
From: helly25 <6420169+helly25@users.noreply.github.com>
Date: Sat, 11 Jul 2026 21:48:25 +0100
Subject: [PATCH 3/9] mbo/hash: smhasher parser - unparseable/invalid-hash log
is FAIL, not PASS
A SMHasher3 log with no "Overall result" line means the battery never
completed (unknown hash name -> "Invalid hash '...' specified" which still
exits 0, a crash, or truncation). Defaulting that to PASS on returncode 0 hid
the failure; treat a missing verdict line as FAIL instead.
---
mbo/hash/measurements/hash_benchmark_report.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/mbo/hash/measurements/hash_benchmark_report.py b/mbo/hash/measurements/hash_benchmark_report.py
index 8b878f8..2eb1ed8 100644
--- a/mbo/hash/measurements/hash_benchmark_report.py
+++ b/mbo/hash/measurements/hash_benchmark_report.py
@@ -279,7 +279,11 @@ def _smhasher_one(cmd_prefix, name, raw_dir, stamp):
proc = subprocess.run([*cmd_prefix, name], capture_output=True, text=True, check=False)
text = proc.stdout + proc.stderr
verdict_match = _SMH_VERDICT_RE.search(text)
- verdict = verdict_match.group(1).upper() if verdict_match else ("PASS" if proc.returncode == 0 else "FAIL")
+ # A real run always prints "Overall result: ...". Its absence means the
+ # battery never completed - an unknown hash name ("Invalid hash '...'
+ # specified", which still exits 0), a crash, or a truncated log - so it must
+ # read as FAIL, never a returncode-0 default PASS that hides the problem.
+ verdict = verdict_match.group(1).upper() if verdict_match else "FAIL"
passed = int(verdict_match.group(2)) if verdict_match and verdict_match.group(2) else None
total = int(verdict_match.group(3)) if verdict_match and verdict_match.group(3) else None
# Failing test/family names (minus the overall-verdict line), so the JSON
From 6f1552eb203200366a6a0a371f91d19aa34d6738 Mon Sep 17 00:00:00 2001
From: helly25 <6420169+helly25@users.noreply.github.com>
Date: Sat, 11 Jul 2026 21:49:18 +0100
Subject: [PATCH 4/9] mbo/hash: note SipHash is a keyed PRF, link the SipHash
paper
---
mbo/hash/README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/mbo/hash/README.md b/mbo/hash/README.md
index 408e516..bd11da2 100644
--- a/mbo/hash/README.md
+++ b/mbo/hash/README.md
@@ -375,7 +375,8 @@ that gets it a clean SMHasher3 pass - but wins the mixed-length latency test,
which is closer to how a hash table actually uses a hash. For 128-bit output,
`jumbo` is fastest from the mid sizes up. `fnv1a` is quickest on 1-3 byte keys,
`dumbo` does well on tiny keys but falls off on large ones, and `siphash` is
-slower throughout - the price of being a keyed PRF.
+slower throughout - the price of being a keyed PRF (Pseudo-Random Function, see
+[SipHash: a fast short-input PRF](https://cr.yp.to/siphash/siphash-20120918.pdf)).
## Quality: SMHasher3
From 6b9fb27c04cf8bbc76e45eca48d4823a5e1a925a Mon Sep 17 00:00:00 2001
From: helly25 <6420169+helly25@users.noreply.github.com>
Date: Sat, 11 Jul 2026 21:56:13 +0100
Subject: [PATCH 5/9] mbo/hash: fix SMHasher3 names for fnv1a (FNV-1a-64) and
murmur3 (MurmurHash3-128)
The container binary rejects the bare names `FNV-1a` and `MurmurHash3`
("Invalid hash '...' specified"), so those two batteries never ran. Use the
variant-qualified names it registers, confirmed against `SMHasher3 --list`;
the bit-widths match the README (fnv1a 64 -> FNV-1a-64, murmur3 128 ->
MurmurHash3-128). A battery re-run is still needed to refresh the bundle data.
---
mbo/hash/measurements/hash_benchmark_report.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/mbo/hash/measurements/hash_benchmark_report.py b/mbo/hash/measurements/hash_benchmark_report.py
index 2eb1ed8..6f95c13 100644
--- a/mbo/hash/measurements/hash_benchmark_report.py
+++ b/mbo/hash/measurements/hash_benchmark_report.py
@@ -77,12 +77,12 @@
"mumbo": ["mumbo-64"],
"jumbo": ["jumbo-128"],
"dumbo": ["dumbo-64"],
- "fnv1a": ["FNV-1a"],
+ "fnv1a": ["FNV-1a-64"],
"xxh64": ["XXH-64"],
"xxh3": ["XXH3-64", "XXH3-128"],
"rapidhash": ["rapidhash"],
"siphash": ["SipHash-2-4"],
- "murmur3": ["MurmurHash3"],
+ "murmur3": ["MurmurHash3-128"],
}
# Default set - ALL algorithms, explicitly including the legacy `dumbo`.
_SMHASHER_ALL = ["mumbo", "jumbo", "dumbo", "fnv1a", "xxh64", "xxh3", "rapidhash", "siphash", "murmur3"]
From ecd84a1d464d0b0a9594351704cb62f33334718b Mon Sep 17 00:00:00 2001
From: helly25 <6420169+helly25@users.noreply.github.com>
Date: Sat, 11 Jul 2026 22:01:11 +0100
Subject: [PATCH 6/9] mbo/hash: lead the intro with the in-house mumbo/jumbo
and dumbo family
---
mbo/hash/README.md | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/mbo/hash/README.md b/mbo/hash/README.md
index bd11da2..11a33d0 100644
--- a/mbo/hash/README.md
+++ b/mbo/hash/README.md
@@ -1,7 +1,13 @@
# mbo/hash - fast, constexpr-safe, non-cryptographic hashing
-Spec-based, fast, constexpr-compatible, Apache-licensed, no-nonsense hash
-implementations. Algorithm reference and API listing: see the
+Fast, constexpr-safe, non-cryptographic hashing, built around the in-house
+**mumbo/jumbo and dumbo** family: notice-free, pure Apache-2.0, and MUM-based
+(widening multiply). `mumbo` (64-bit) and its native 128-bit sibling `jumbo`
+both pass SMHasher3 clean (188/188 - `jumbo` is the only clean native 128 we
+measured) and post the best mixed-length latency in our benchmarks; `dumbo` is
+the compact companion. The third-party algorithms (rapidhash, xxh3/xxh64,
+murmur3, siphash, fnv1a) are exact transcriptions, kept for interop and
+comparison. Algorithm reference and API listing: see the
[repository README](../../README.md). Quality (SMHasher3) and performance
measurements for all algorithms: below.
From 3ca39dc27fb1491ac525998d6c99461567a38788 Mon Sep 17 00:00:00 2001
From: helly25 <6420169+helly25@users.noreply.github.com>
Date: Sat, 11 Jul 2026 22:06:22 +0100
Subject: [PATCH 7/9] mbo/hash: intro - note the restricted compile-time mangle
with release-time enforcement
---
mbo/hash/README.md | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/mbo/hash/README.md b/mbo/hash/README.md
index 11a33d0..d800c6d 100644
--- a/mbo/hash/README.md
+++ b/mbo/hash/README.md
@@ -5,9 +5,14 @@ Fast, constexpr-safe, non-cryptographic hashing, built around the in-house
(widening multiply). `mumbo` (64-bit) and its native 128-bit sibling `jumbo`
both pass SMHasher3 clean (188/188 - `jumbo` is the only clean native 128 we
measured) and post the best mixed-length latency in our benchmarks; `dumbo` is
-the compact companion. The third-party algorithms (rapidhash, xxh3/xxh64,
-murmur3, siphash, fnv1a) are exact transcriptions, kept for interop and
-comparison. Algorithm reference and API listing: see the
+the compact companion. It also ships a **build-seed mangle**
+(`hash_mangle.h`): restricted, constexpr-safe compile-time hash mangling with
+release-time rotation enforced - the `MODULE.bazel` version is folded into a
+bucketed build constant, so every release necessarily changes the hash and
+values can never silently become a persistence or wire format. That is hash
+randomization for the constexpr world, which compile-time hashing otherwise
+rules out. The third-party algorithms (rapidhash, xxh3/xxh64, murmur3, siphash,
+fnv1a) are exact transcriptions, kept for interop and comparison. Algorithm reference and API listing: see the
[repository README](../../README.md). Quality (SMHasher3) and performance
measurements for all algorithms: below.
From 44b3be5285b8f3c6ae0f31d43cd6f45e0ae24188 Mon Sep 17 00:00:00 2001
From: helly25 <6420169+helly25@users.noreply.github.com>
Date: Sat, 11 Jul 2026 22:10:58 +0100
Subject: [PATCH 8/9] mbo/hash: link SMHasher3 on first mention (intro); de-dup
the later link; typo
---
mbo/hash/README.md | 26 ++++++++++++++------------
1 file changed, 14 insertions(+), 12 deletions(-)
diff --git a/mbo/hash/README.md b/mbo/hash/README.md
index d800c6d..4b2ec06 100644
--- a/mbo/hash/README.md
+++ b/mbo/hash/README.md
@@ -3,18 +3,20 @@
Fast, constexpr-safe, non-cryptographic hashing, built around the in-house
**mumbo/jumbo and dumbo** family: notice-free, pure Apache-2.0, and MUM-based
(widening multiply). `mumbo` (64-bit) and its native 128-bit sibling `jumbo`
-both pass SMHasher3 clean (188/188 - `jumbo` is the only clean native 128 we
+both pass [SMHasher3](https://gitlab.com/fwojcik/smhasher3) clean (188/188 -
+`jumbo` is the only clean native 128 we
measured) and post the best mixed-length latency in our benchmarks; `dumbo` is
-the compact companion. It also ships a **build-seed mangle**
-(`hash_mangle.h`): restricted, constexpr-safe compile-time hash mangling with
-release-time rotation enforced - the `MODULE.bazel` version is folded into a
-bucketed build constant, so every release necessarily changes the hash and
-values can never silently become a persistence or wire format. That is hash
-randomization for the constexpr world, which compile-time hashing otherwise
-rules out. The third-party algorithms (rapidhash, xxh3/xxh64, murmur3, siphash,
-fnv1a) are exact transcriptions, kept for interop and comparison. Algorithm reference and API listing: see the
-[repository README](../../README.md). Quality (SMHasher3) and performance
-measurements for all algorithms: below.
+the compact companion.
+
+It also ships a **build-seed mangle** (`hash_mangle.h`): restricted/limited,
+constexpr-safe compile-time hash mangling with release-time rotation enforcement.
+That is hash randomization for the constexpr world, which compile-time hashing
+otherwise rules out.
+
+The third-party algorithms (rapidhash, xxh3/xxh64, murmur3, siphash, fnv1a) are
+exact transcriptions, kept for interop and comparison. Algorithm reference and
+API listing: see the [repository README](../../README.md). Last but not least we
+provide quality (SMHasher3) and performance measurements for all algorithms below.
## Offerings
@@ -391,7 +393,7 @@ slower throughout - the price of being a keyed PRF (Pseudo-Random Function, see
## Quality: SMHasher3
-[SMHasher3](https://gitlab.com/fwojcik/smhasher3) is the research-grade hash
+SMHasher3 is the research-grade hash
test battery; passing it is the community bar for a production-quality
general-purpose hash. All results below are **our own measurements on one
rig** (same build, container, flags, and machine - see Methodology), so the
From fdd0f30a00e8f75dce90930b9f28ef0a4b97b03f Mon Sep 17 00:00:00 2001
From: helly25 <6420169+helly25@users.noreply.github.com>
Date: Sat, 11 Jul 2026 22:13:08 +0100
Subject: [PATCH 9/9] mbo/hash: restore SMHasher3 link in the Quality section
too (linked intro + section)
---
mbo/hash/README.md | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/mbo/hash/README.md b/mbo/hash/README.md
index 4b2ec06..469e2c4 100644
--- a/mbo/hash/README.md
+++ b/mbo/hash/README.md
@@ -4,9 +4,8 @@ Fast, constexpr-safe, non-cryptographic hashing, built around the in-house
**mumbo/jumbo and dumbo** family: notice-free, pure Apache-2.0, and MUM-based
(widening multiply). `mumbo` (64-bit) and its native 128-bit sibling `jumbo`
both pass [SMHasher3](https://gitlab.com/fwojcik/smhasher3) clean (188/188 -
-`jumbo` is the only clean native 128 we
-measured) and post the best mixed-length latency in our benchmarks; `dumbo` is
-the compact companion.
+`jumbo` is the only clean native 128 we measured) and post the best mixed-length
+latency in our benchmarks; `dumbo` is the compact companion.
It also ships a **build-seed mangle** (`hash_mangle.h`): restricted/limited,
constexpr-safe compile-time hash mangling with release-time rotation enforcement.
@@ -393,7 +392,7 @@ slower throughout - the price of being a keyed PRF (Pseudo-Random Function, see
## Quality: SMHasher3
-SMHasher3 is the research-grade hash
+[SMHasher3](https://gitlab.com/fwojcik/smhasher3) is the research-grade hash
test battery; passing it is the community bar for a production-quality
general-purpose hash. All results below are **our own measurements on one
rig** (same build, container, flags, and machine - see Methodology), so the