feat(hash): add canonical FNV-1a, XXH64, and MurmurHash3 implementations - #209
Merged
Conversation
Adds three well-known hash algorithms as constexpr-safe, dependency-free headers, each in its own namespace and pluggable via GetHash<&Fn>: - mbo::hash::fnv1a::GetHash64 -- FNV-1a 64 (public domain) - mbo::hash::xxh64::GetHash64 -- XXH64 / xxHash 64-bit (BSD-2 spec) - mbo::hash::murmur3::GetHash64/GetHash128 -- MurmurHash3 x64 128 (public domain) All three produce the published reference values on every platform (the algorithms are little-endian defined; loads use the endian-independent hash_internal helpers, with a new Load32 for XXH64's tail). Known-answer tests pin values generated with the reference implementations (python xxhash / mmh3) across each algorithm's block/tail boundaries and seeds. The algorithms register as descriptors in hash_test_util.h, so the typed framework covers them automatically (constexpr==runtime, collisions, avalanche, 64/128-bit detection -- murmur3 is the second 128-bit-based algorithm). The typed Hash128 test no longer assumes the mh fold relation; Get64/Get128 relations are per-algorithm tests (mh folds, murmur3 truncates to h1). Benchmark now compares all five algorithms.
… recipe Promote hash_internal::Hash128To64 into mbo::hash so composing a fold-mixed 64-bit value from any 128-bit based algorithm is official API, with the recipe documented at the alias: uint64_t hash = mbo::hash::Hash128To64(mbo::hash::murmur3::GetHash128(data)); murmur3::GetHash64 stays the canonical h1 truncation (ecosystem contract); the fold is the user-composable alternative. Adds a test pinning the recipe.
The typed test suite and the benchmark each maintained their own algorithm registrations, so a new descriptor could be added to one and forgotten in the other. Introduce algo::AllAlgorithms (a tuple of all descriptors) in hash_test_util.h as the single source of truth: - hash_test.cc converts the tuple into the gtest type list. - hash_benchmark.cc registers benchmarks by folding over the tuple, adding the 128-bit benchmark automatically where HasHash128 detects one (custom main with benchmark::RegisterBenchmark replaces the per-algorithm macros). Adding a descriptor to AllAlgorithms now tests AND benchmarks it.
Fab-Cat
approved these changes
Jul 2, 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.
Follow-up to #208: more hash algorithms, plugged into the templated test/benchmark framework.
What
Three well-known algorithms as constexpr-safe, dependency-free headers, each in its own namespace and usable via
GetHash<&Fn>:mbo::hash::fnv1a::GetHash64— FNV-1a 64 (public domain)mbo::hash::xxh64::GetHash64— XXH64 / xxHash 64-bit (BSD-2 spec)mbo::hash::murmur3::GetHash64/GetHash128— MurmurHash3 x64 128 (public domain); the second 128-bit-based algorithm, exercising the framework's width detectionAll produce the published reference values on every platform — the algorithms are little-endian defined and the loads use the endian-independent
hash_internalhelpers (newLoad32for XXH64's tail).mbo::hash::Hash128To64is now public API, with the fold recipe documented at the alias:Hash128To64(murmur3::GetHash128(data))gives a fold-mixed (non-canonical) 64-bit murmur3;murmur3::GetHash64stays the canonicalh1truncation.Testing
xxhash/mmh3, FNV per spec), covering each algorithm's block/tail boundaries and non-zero seeds. These caught a wrong Murmur3 constant during development — the vectors are doing their job.algo::AllAlgorithmsinhash_test_util.his the single source of truth — the typed test suite and the benchmark both derive from it, so one added descriptor is tested and benchmarked automatically (128-bit benchmarks auto-register whereHasHash128detects them).Benchmark
Apple M-series,
bazel run -c opt //mbo/hash:hash_benchmark -- --benchmark_min_time=0.2s. Throughput in GiB/s (higher is better), random data per length.64-bit (
GetHash64)128-bit (
GetHash128)Takeaways:
mhis the best all-rounder at typical key sizes (≈9.8 GiB/s @ 16 B);xxh64's 4-accumulator stripes dominate from ~64 B up (≈17 GiB/s peak) — a possible future direction formh's block loop;fnv1aonly wins at ~1 byte and falls off hard;murmur3is a solid mid-fielder and slightly beatsmhon large 128-bit hashing.