A portable, accurate, header-only C++20 implementation of rapidhash v3 designed for compile-time (constexpr) evaluation.
Works across most platforms :
- No compiler intrinsics
- Forces little-endian
The trade-off for compile-time support is that a few optimizations had to be sacrificed. In practice, however, the performance impact is small and depends on your workload. See the Benchmarks section for details.
Note
This implementation contains only the classic version of rapidhash v3. The Micro/Nano versions and macro modifiers are not yet supported.
- API
- Available functions
- Examples
- Benchmarks
- Results
- Building the benchmarks
- Tests
- Types
- Building the tests
The following functions are available in the rapidhash::cexpr namespace :
- For raw buffers :
rapidhash_with_seedrapidhash
- For strings :
rapidhash_string_with_seedrapidhash_string
- For integrals :
rapidhash_integral_with_seedrapidhash_integral
The default seed (when using functions that do not specify it) is set to 0.
#include "rapidhash_cexpr.h"
#include <cstddef>
#include <string>
#include <cstdint>
constexpr std::array<const std::byte, 100> kArray{};
constexpr std::string kMessage = "Hello World!";
constexpr std::uint64_t kInt = 28;
constexpr std::uint64_t kSeed = 42;
/*-------------------------*/
using rapidhash::cexpr;
// Takes a std::span<const std::byte>.
// The array stores the same type so we can take this shortcut here.
//
// Tip : You can use std::as_bytes to convert easily to a compatible span.
constexpr std::uint64_t kRawHash = rapidhash(std::span{ kArray });
constexpr std::uint64_t kSaltedRawHash = rapidhash_with_seed(std::span{ kArray }, kSeed);
// Takes a std::string_view, so you can take advantage of implicit construction.
constexpr std::uint64_t kStringHash = rapidhash_string(kMessage);
constexpr std::uint64_t kSaltedStringHash = rapidhash_string_with_seed(kMessage, kSeed);
// Works with any integral type.
constexpr std::uint64_t kIntHash = rapidhash_integral(kInt);
constexpr std::uint64_t kSaltedIntHash = rapidhash_integral_with_seed(kInt, kSeed);
// rapidhash is a one-shot hash function so, if you want to simulate a state to mix multiple
// elements together, you can do it by using the last hash as a seed for the next one :
constexpr std::uint64_t kStringIntMix = rapidhash_integral_with_seed(kMessage, kIntHash);Benchmarking is done in runtime to allow comparison with the original algorithm. However, the constexpr algorithm remains identical, so you should hit similar results during compilation.
Environment :
- CPU : Intel Core Ultra 5 125U (Meteor Lake-M/P)
- RAM : SK hynix 16Go DDR5 SO-DIMM PC5-5600
- OS : Windows 11 24H2
Compiler : clang version 22.1.8
Flags :
- bench framework : -std=c99 -Wall -Wextra -Wstrict-aliasing=1 -O2
- hash (compatibility layer) : -std=c++20 -Wall -Wextra -O2
| Test / Metric | Unit | Constexpr vs Original (summary) | Key result |
|---|---|---|---|
| Large-input throughput (256 B → 128 MiB) | MB/s | 40,714 vs 40,464 (+0.6%) | Essentially identical; size-dependent variation from −10.1% to +14.0% |
| Small fixed-size throughput (1–127 B) | M hashes/s | 233.3 vs 299.6 (−22.1%) | Major slowdown; worst per-size loss −38.3% |
| Small random-size throughput ([1–N] B) | M hashes/s | 273.2 vs 349.0 (−21.7%) | Major slowdown; worst per-size loss −35.0% |
| Small fixed-size latency chain | M hashes/s (9.56 vs 9.61 ns/hash) | 104.6 vs 104.1 (+0.5%) | Same latency performance; mostly unchanged |
| Small random-size latency chain | M hashes/s (8.07 vs 8.01 ns/hash) | 123.9 vs 124.8 (−0.7%) | Same latency performance; negligible difference |
| Small fixed throughput by size trend | M hashes/s | Loss ranges −12.5% → −38.3% | Largest regressions around 1–16 B and 97–112 B |
| Small random throughput by size trend | M hashes/s | Loss ranges −7.0% → −35.0% | Consistent slowdown across all sizes |
| Latency by size trend | M hashes/s | Usually within ±2% | No meaningful regression |
| Run reproducibility | % variation | < 2% between runs | Results are stable |
As you can observe, there is a noticeable performance degradation for small fixed-size throughput workloads, with a measured loss of approximately 35–40%.
However this constexpr version remains a high-quality algorithm, maintaining almost identical latency and only a very small slowdown for large key sizes.
Simply run :
make benchThis builds the bench executable in the project's root build directory. If the build
directory does not already exist, it will be created automatically.
We carefully test in Github Actions multiple aspects of this rewrite :
-
Matching test : verifies that the outputs for all possible algorithm branches matches the official implementation's. This way, we ensure that we distribute a faithful rapidhash v3 rewrite.
-
Endianness test : verifies that our implementation works correctly on little/big-endian.
-
Documentation test : checks if the documentation examples in this README actually compile.
-
Compiler matrix : ensures that our implementation can build correctly on supported compilers.
Simply run :
make testsThis builds the test executable in the project's root build directory. If the build
directory does not already exist, it will be created automatically.