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: + -![mbo/hash 64-bit one-shot throughput vs key length, log-log](measurements/hash_throughput_64.svg) +### Apple M5 Pro · arm64 · 18-core · clang-22 · 1417af5e -![mbo/hash 128-bit one-shot throughput vs key length, log-log](measurements/hash_throughput_128.svg) +![mbo/hash 64-bit throughput vs key length, log-log](measurements/charts/macos-arm64-apple-m5-pro_clang-22_64.svg) +![mbo/hash 128-bit throughput vs key length, log-log](measurements/charts/macos-arm64-apple-m5-pro_clang-22_128.svg) -### 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 + +![mbo/hash 64-bit throughput vs key length, log-log](measurements/charts/linux-x86-64-amd-ryzen-9-9950x-16-core-processor_gcc-15_64.svg) +![mbo/hash 128-bit throughput vs key length, log-log](measurements/charts/linux-x86-64-amd-ryzen-9-9950x-16-core-processor_gcc-15_128.svg) + + + +#### 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 @@ + + +mbo/hash - 128-bit one-shot throughput +AMD Ryzen 9 9950X 16-Core Processor · x86_64 · 10-core · gcc-15 · 1417af5e + +10 + +100 + +1B + + +4B + + +16B + + +64B + + +256B + + +1Ki + + +4Ki +key length (log scale) +ns / op (log scale) + + +jumbo + + +xxh3 + + +murmur3 + 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 @@ + + +mbo/hash - 64-bit one-shot throughput +AMD Ryzen 9 9950X 16-Core Processor · x86_64 · 10-core · gcc-15 · 1417af5e + +1 + +10 + +100 + +1000 + +1B + + +4B + + +16B + + +64B + + +256B + + +1Ki + + +4Ki +key length (log scale) +ns / op (log scale) + + +mumbo + + +rapidhash + + +xxh3 + + +xxh64 + + +murmur3 + + +siphash24 + + +fnv1a + + +dumbo + 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 @@ + + +mbo/hash - 128-bit one-shot throughput +Apple M5 Pro · arm64 · 18-core · clang-22 · 1417af5e + +10 + +100 + +1B + + +4B + + +16B + + +64B + + +256B + + +1Ki + + +4Ki +key length (log scale) +ns / op (log scale) + + +jumbo + + +xxh3 + + +murmur3 + 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 @@ + + +mbo/hash - 64-bit one-shot throughput +Apple M5 Pro · arm64 · 18-core · clang-22 · 1417af5e + +1 + +10 + +100 + +1000 + +1B + + +4B + + +16B + + +64B + + +256B + + +1Ki + + +4Ki +key length (log scale) +ns / op (log scale) + + +mumbo + + +rapidhash + + +xxh3 + + +xxh64 + + +murmur3 + + +siphash24 + + +fnv1a + + +dumbo + 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