mbo/hash: redesign dumbo-64 into a compact single-lane MUM hash (40 -> 188/188, 2-3x faster) - #241
Merged
Conversation
The legacy dumbo used silly constants (multiply by 6571, add 17/193, a 104729 tail) and a multi-op per-4-byte step: barely diffused (SMHasher3 40/188) and slow. Rebuilt the measured way, keeping it deliberately minimal next to mumbo (one accumulator, one 8-byte word/step, no small-key switch, no lanes, no streaming, no 128-bit, stock Fmix64 finalizer): - nothing-up-my-sleeve constants (golden ratio, sqrt-prime fractions) - the MUM primitive Mul128Fold64(word^kWord, state^kState): both operands state/data dependent, so the product is quadratic (a constant multiplier is linear and fails collisions/distribution no matter the constant) - length folded at the end (folding it at init let a single low bit cancel a length delta and collide 1-bit with all-zero keys) - shared endian-safe Load64/LoadTail: no raw memcpy, no signed-char bug, const-eval == runtime on every target (incl. cross/big-endian) SMHasher3 FAIL 186/188 (from 40); ~2-3x faster than legacy for >=8 B and the fastest hash in the suite for <=16 B. The two residual failures are both in SeedZeroes (weak seeding: for zero-data keys the seed enters one operand linearly) - closing them (stronger finalizer/seed mixing) is the tracked next step, deferred to keep dumbo minimal. Values changed (dumbo is not stable across versions); plugin verification re-baked; README quality tables + a dumbo design-iterations narrative added.
Follow-on to the dumbo redesign (which reached 186/188): the two residual failures were both SMHasher3 SeedZeroes. Root cause: dumbo is single-lane, so for zero-data keys a seed folded only at init rides one weakly-mixing chain and reaches the finalizer nearly linear (mumbo avoids this via 8 seeded bulk lanes). Fix: replace the fmix64 finalizer with a two-multiply widening avalanche that keeps BOTH halves of the first product and mixes them, and inject the seed into a product operand directly -> the output is quadratic in the seed even for zero-data keys, clearing both SeedZeroes windows. Result: clean PASS 188/188, and dumbo now has strong (not merely reactive) avalanche (hash_test kStrongAvalanche flipped to true). Cost: the two widening multiplies are ~0.15-0.35 ns slower on tiny keys than fmix64, still the fastest hash in the suite at 7-8 B. Plugin verification re-baked; README (quality tables now PASS, perf column, v3 iteration) + measurements updated. The loop micro-opts (end-pointer ptr<limit, likely/unlikely) were measured and rejected: the loop is latency-bound on the serial MUM chain, so the leaner form is off the critical path (clean A/B showed no difference).
helly25
enabled auto-merge (squash)
July 9, 2026 05:59
Fab-Cat
approved these changes
Jul 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebuilds the legacy
dumbohash the measured way, addressing both problems the review flagged: silly constants and a slow multi-op core. Lands clean at SMHasher3 188/188.Result
What changed
6571,17,193,104729) -> nothing-up-my-sleeve (golden ratio + sqrt-prime fractions).Mul128Fold64(word ^ kWord, state ^ kState)MUM step over 8-byte words. Both operands state/data dependent -> product is quadratic; a constant multiplier is linear and fails collisions/distribution no matter the constant.Load64/LoadTail-- no rawmemcpy, no signed-charbug,const-eval == runtimeon every target (incl. cross/big-endian).mumbo: one accumulator, one 8-byte word/step, no small-key switch, no lanes, no streaming, no 128-bit form.Measured design iterations (in README)
legacy 40/188 -> v1 (non-widening, 132/188, exposes the constant-multiplier linearity) -> v2 (MUM core, 186/188, residual SeedZeroes) -> v3 (seed-at-finalize, 188/188).
Tradeoffs (documented)
fmix64would be (still fastest at 7-8 B).ptr < limit,[[likely]]/[[unlikely]]) were measured and rejected: the loop is latency-bound on the serial MUM chain, so the leaner form is off the critical path (clean A/B showed no difference).Docs
mainCI run).hash_testgreen (avalanche + seed-avalanche now asserted strong, distribution, structured-key distinctness, const-eval == runtime).