Skip to content

Commit ba3ec61

Browse files
authored
Merge pull request #43 from hoxxep/v3-micro-mismatch
Rapidhash v4.0.0: V3 micro/nano mismatch, performance improvements, refactoring
2 parents e6a431a + c81c7e6 commit ba3ec61

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+792
-471
lines changed

.github/workflows/rust.yaml

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
run: cargo test --no-fail-fast
2323

2424
test-no-std:
25-
name: "Test (no_std)"
25+
name: "Test (no-std)"
2626
runs-on: ubuntu-latest
2727
steps:
2828
- uses: actions/checkout@v5
@@ -43,33 +43,50 @@ jobs:
4343
- name: Compile and run tests
4444
run: cargo test -p rapidhash --no-fail-fast --lib --tests --features=unsafe
4545

46-
test-all-features:
47-
name: "Test (all features)"
46+
test-nightly-all-features:
47+
name: "Test (nightly, all features)"
4848
runs-on: ubuntu-latest
4949
steps:
5050
- uses: actions/checkout@v5
5151
- uses: dtolnay/rust-toolchain@stable
52+
with:
53+
toolchain: nightly
5254
- name: Rust dependency cache
5355
uses: Swatinem/rust-cache@v2
5456
- name: Compile and run tests
55-
run: cargo test -p rapidhash --no-fail-fast --lib --tests --all-features
57+
run: cargo +nightly test -p rapidhash --no-fail-fast --lib --tests --all-features
58+
59+
test-nightly-no-std:
60+
name: "Test (nightly, no-std)"
61+
runs-on: ubuntu-latest
62+
steps:
63+
- uses: actions/checkout@v5
64+
- uses: dtolnay/rust-toolchain@stable
65+
with:
66+
toolchain: nightly
67+
- name: Rust dependency cache
68+
uses: Swatinem/rust-cache@v2
69+
- name: Compile and run tests (no_std)
70+
run: cargo +nightly test -p rapidhash --no-fail-fast --lib --tests --no-default-features
5671

5772
test-msrv:
5873
name: "Test (MSRV)"
5974
runs-on: ubuntu-latest
6075
env:
61-
RUST_VERSION: 1.77.0
76+
RUST_VERSION: 1.71.0
6277
steps:
6378
- uses: actions/checkout@v5
6479
- name: Rust dependency cache
6580
uses: Swatinem/rust-cache@v2
6681
- name: Install Rust (rustup, ${{ env.RUST_VERSION }})
6782
shell: bash
6883
run: rustup update ${{ env.RUST_VERSION }} --no-self-update && rustup default ${{ env.RUST_VERSION }} && cargo -V && rustc -V
69-
- name: Compile and run tests (std)
70-
run: cargo test -p rapidhash --no-fail-fast --all-features
71-
- name: Compile and run tests (no_std)
72-
run: cargo test -p rapidhash --no-fail-fast --lib --tests --no-default-features
84+
- name: Compile and run tests (no-std)
85+
working-directory: rapidhash-msrv
86+
run: cargo test --no-fail-fast --no-default-features
87+
- name: Compile and run tests (all features)
88+
working-directory: rapidhash-msrv
89+
run: cargo test --no-fail-fast --all-features
7390

7491
test-c:
7592
name: "Test (C implementations)"

CHANGELOG.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,32 @@
11
# Changelog
22

3+
## 4.0.0 (20250901)
4+
5+
https://github.com/hoxxep/rapidhash/pull/43
6+
7+
### Breaking changes
8+
- **`rapidhash::v3` micro/nano output change:** input lengths 5-7 were mismatching the intended C++ V3 output. The C++ rapidhash V3 has been [yanked and re-released as V3](https://github.com/Nicoshev/rapidhash/issues/33) to fix the bug, and this rust implementation will follow. This changes the hash outputs for `rapidhash_v3_micro_inline` and `rapidhash_v3_nano_inline` for inputs of size 5, 6, and 7 bytes.
9+
- **`RapidBuildHasher` renamed and refactored** to `SeedableState`.
10+
- **`RapidHasher<'s>` new lifetime parameter** added to support user-defined secrets via `SeedableState`.
11+
- **`RapidHashMap` and `RapidHashSet` moved to crate top level** for convenience. The top level uses the `fast::` variants, and the `quality::` and `inner::` hashmaps have been removed. They can still be built manually using `inner::RandomState` if required. The `fast::` collection variants have been deprecated to be removed in a future major release.
12+
13+
### Additions
14+
- **`nightly` feature** which improves str hashing performance by omitting the `0xFF` suffix write and adds likely/unlikely hints.
15+
- **`SeedableState`**: a hasher builder which can be seeded with fixed or user-defined secrets. This replaces `RapidBuildHasher`, but still defaults to random seeds. It is slightly slower than `RandomState`.
16+
17+
### Performance improvements
18+
- **Bounds check elision**: Improved `RapidHasher` by eliding extra bounds checks in some cases by using `assert_unchecked`.
19+
- **Likely/unlikely hints**: Added stable likely/unlikely hints in various places to ensure small inputs are favoured.
20+
21+
### MSRV
22+
- **MSRV reduced to 1.71.0** from 1.77.0 by removing const usage of `first_chunk`.
23+
324
## 3.1.0 (20250809)
425

5-
## Performance improvements
26+
### Performance improvements
627
- Improved `RapidHasher` small string hashing performance by 1.5-15% depending on the benchmark, by reducing the small string hashing code size and allowing the compiler to inline more. Performance was also improved on big-endian platforms by reading native-endian bytes. The portable hashers (`rapidhash::v3` etc. modules) are unaffected by this change. [#37](https://github.com/hoxxep/rapidhash/pull/37)
728

8-
## Fixes
29+
### Fixes
930
- Fixed compilation on targets without atomic pointers. [#38](https://github.com/hoxxep/rapidhash/issues/38), [#39](https://github.com/hoxxep/rapidhash/pull/39)
1031

1132
## 3.0.0 (20250730)

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[workspace]
22
resolver = "2"
33
members = [ "rapidhash", "rapidhash-bench", "rapidhash-bench-wasm", "rapidhash-c"]
4+
exclude = [ "rapidhash-msrv" ]
45

56
[workspace.dependencies]
67
rapidhash = { path = "rapidhash", default-features = false }

README.md

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ A rust implementation of [rapidhash](https://github.com/Nicoshev/rapidhash), the
44

55
- **High quality**, the fastest hash passing all tests in the SMHasher and SMHasher3 benchmarks. Collision-based study showed a collision probability that's close to ideal.
66
- **Very fast**, the fastest passing hash in SMHasher3. Significant peak throughput improvement over wyhash and foldhash. Fastest platform-independent hash. Fastest const hash.
7-
- **Platform independent**, works on all platforms, no dependency on machine-specific vectorized or cryptographic hardware instructions. Optimised for both AMD64 and AArch64.
7+
- **Platform independent and no-std compatible**, works on all platforms, no dependency on machine-specific vectorized or cryptographic hardware instructions. Optimised for both AMD64 and AArch64.
88
- **Memory safe**, when the `unsafe` feature is disabled (default). This implementation has also been fuzz-tested with `cargo fuzz`.
9-
- **No dependencies and no-std compatible** when disabling default features.
109
- **Official successor to wyhash** with improved speed, quality, and compatibility.
1110
- **Run-time and compile-time hashing** as the hash implementation is fully `const`.
1211
- **Idiomatic** `std::hash::Hasher` compatible hasher for `HashMap` and `HashSet`.
@@ -21,12 +20,12 @@ A rust implementation of [rapidhash](https://github.com/Nicoshev/rapidhash), the
2120
Following rust's `std::hash` traits, the underlying hash function may change between minor versions, and is only suitable for in-memory hashing. These types are optimised for speed and minimal DoS resistance, available in the `rapidhash::fast` and `rapidhash::quality` flavours.
2221

2322
- `RapidHasher`: A `std::hash::Hasher` compatible hasher that uses the rapidhash algorithm.
24-
- `RapidHashBuilder`: A `std::hash::BuildHasher` for initialising the hasher with the default seed and secrets.
2523
- `RandomState`: A `std::hash::BuildHasher` for initialising the hasher with a random seed and secrets.
26-
- `RapidHashMap` and `RapidHashSet`: Helper types for using `RapidHasher` with `HashMap` and `HashSet`.
24+
- `SeedableState`: A `std::hash::BuildHasher` for initialising the hasher with the custom seed and secrets.
25+
- `RapidHashMap` and `RapidHashSet`: Helper types for using `fast::RandomState` with `HashMap` and `HashSet`.
2726

2827
```rust
29-
use rapidhash::fast::RapidHashMap;
28+
use rapidhash::RapidHashMap;
3029

3130
// A HashMap using RapidHasher for fast in-memory hashing.
3231
let mut map = RapidHashMap::default();
@@ -35,11 +34,11 @@ map.insert("key", "value");
3534

3635
```rust
3736
use std::hash::BuildHasher;
38-
use rapidhash::quality::RapidBuildHasher;
37+
use rapidhash::quality::SeedableState;
3938

4039
// Using the RapidHasher directly for in-memory hashing.
41-
let hasher = RapidBuildHasher::default();
42-
assert_eq!(hasher.hash_one(b"hello world"), 9938606849760368330);
40+
let hasher = SeedableState::fixed();
41+
assert_eq!(hasher.hash_one(b"hello world"), 3348275917668072623);
4342
```
4443

4544
### Portable Hashing
@@ -94,6 +93,7 @@ echo "example" | rapidhash --v3
9493
- `rand`: Enables using the `rand` library to more securely initialise `RandomState`. Includes the `rand` crate dependency.
9594
- `rng`: Enables `RapidRng`, a fast, non-cryptographic PRNG based on rapidrng. Includes the `rand_core` crate dependency.
9695
- `unsafe`: Uses unsafe pointer arithmetic to skip some unnecessary bounds checks for a small 3-4% performance improvement.
96+
- `nightly`: Enable nightly-only features for even faster hashing, such as overriding `Hasher::write_str` and likely hints.
9797

9898
## Benchmarks
9999

@@ -241,6 +241,13 @@ The benchmarks have been compiled with and without `-C target-cpu=native` on a v
241241
- **Comparison to gxhash**: gxhash achieves its high throughput by using AES instructions and consistently outperforms the other accelerated hashers (ahash, th1a, xxhash3_64). It's a great hash function, but is not a portable hash function, requiring `target-cpu=native` or specific feature flags to compile. Gxhash is a great choice for applications that can guarantee the availability of AES instructions and mostly hash strings, but rapidhash may be preferred for hashing tuples and structs, or by libraries that aim to support a wide range of platforms.
242242
- The default rust hasher (SipHasher) unexpectedly appears to run consistently faster _without_ `target-cpu=native` on various x86 and ARM chips.
243243
- Benchmark your own use case, with your real world dataset! We suggest experimenting with different hash functions to see which one works best for your use case. Rapidhash is great for fast general-purpose hashing in libraries and applications that only need minimal DoS resistance, but certain hashers will outperform for specific use cases.
244+
- We recommend using `lto = "fat"` and `codegen-units = 1` in your `Cargo.toml` release and bench profiles to ensure consistent inlining, application performance, and benchmarking results. For example:
245+
```toml
246+
[profile.release]
247+
opt-level = 3
248+
lto = "fat"
249+
codegen-units = 1
250+
```
244251

245252
</details>
246253

@@ -260,14 +267,14 @@ C++ compatibility is presented in `rapidhash::v1`, `rapidhash::v2`, and `rapidha
260267
Rapidhash V3 is the recommended, fastest, and most recent version of the hash. Streaming is only possible with the rapidhash V3 algorithm. Others are provided for backwards compatibility.
261268

262269
### In-Memory Hashing
263-
Rust hasing traits (`RapidHasher`, `RapidBuildHasher`, etc.) are implemented in `rapidhash::fast`, `rapidhash::quality`, and `rapidhash::inner` modules. These are not guaranteed to give a consistent hash output between platforms, compiler versions, or crate versions as the rust `Hasher` trait [is not suitable](https://github.com/hoxxep/portable-hash/?tab=readme-ov-file#whats-wrong-with-the-stdhash-traits) for portable hashing.
270+
Rust hashing traits (`RapidHasher`, `RandomState`, etc.) are implemented in `rapidhash::fast`, `rapidhash::quality`, and `rapidhash::inner` modules. These are not guaranteed to give a consistent hash output between platforms, compiler versions, or crate versions as the rust `Hasher` trait [is not suitable](https://github.com/hoxxep/portable-hash/?tab=readme-ov-file#whats-wrong-with-the-stdhash-traits) for portable hashing.
264271

265272
- Use `rapidhash::fast` for optimal hashing speed with a slightly lower hash quality. Best for most datastructures such as HashMap and HashSet usage.
266273
- Use `rapidhash::quality` where statistical hash quality is the priority, such as HyperLogLog or MinHash algorithms.
267274
- Use `rapidhash::inner` to set advanced parameters to configure the hash function specifically to your use case.
268275

269276
## Crate Versioning
270-
The minimum supported Rust version (MSRV) is 1.77.0.
277+
The minimum supported Rust version (MSRV) is 1.71.0.
271278

272279
The rapidhash crate follows the following versioning scheme:
273280
- Major for breaking API changes and MSRV version bumps or any changes to `rapidhash_v*` method output.

docs/CHEATSHEET.md

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ cd rapidhash
3333
## Tests
3434

3535
# Run tests
36-
cargo test --all-features
36+
cargo +nightly test --all-features
3737

3838
# Run tests, for no_std with std = off and unsafe = off
3939
cargo test --no-default-features --lib
4040

4141
# Check MSRV
42-
cargo +1.77.0 test --all-features
42+
cargo +1.77.0 test --features=std,rand,rng
4343
```
4444

4545
## Running benchmarks
@@ -50,36 +50,42 @@ Benchmarks are run using `cargo-criterion` in the `rapidhash-bench` crate to sep
5050
cd rapidhash-bench
5151

5252
# Run all benchmarks (assumes cargo-criterion is installed)
53-
RUSTFLAGS="-C target-cpu=native" cargo criterion --bench bench --all-features
53+
RUSTFLAGS="-C target-cpu=native" cargo criterion --bench bench --features bench
5454

5555
# Run all benchmarks, but unsafe=disabled
5656
RUSTFLAGS="-C target-cpu=native" cargo criterion --bench bench --features bench
5757

58+
# Run all benchmarks, with nightly
59+
RUSTFLAGS="-C target-cpu=native" cargo +nightly criterion --bench bench --features bench,nightly
60+
5861
# Run the realworld benchmark, which is a modification of the foldhash benchmarks
59-
RUSTFLAGS="-C target-cpu=native" cargo criterion --bench realworld --all-features
62+
RUSTFLAGS="-C target-cpu=native" cargo criterion --bench realworld --features bench
6063

6164
# Run quality tests across various hash functions
62-
RUSTFLAGS="-C target-cpu=native" cargo bench --bench quality --all-features
65+
RUSTFLAGS="-C target-cpu=native" cargo bench --bench quality --features bench
6366

6467
# Run iai-callgrind to compare instruction counts and L1 cache misses
6568
# Requires: valgrind
66-
RUSTFLAGS="-C target-cpu=native" cargo bench --bench iai-callgrind --all-features
69+
RUSTFLAGS="-C target-cpu=native" cargo bench --bench iai-callgrind --features bench
6770

6871
# Use cargo-instruments to diagnose performance
6972
# Requires: cargo-instruments and MacOS
70-
RUSTFLAGS="-C target-cpu=native" cargo instruments -t time --profile=bench --bench realworld --features bench,unsafe -- --bench hashonly-struuid-rapidhash-v2
73+
RUSTFLAGS="-C target-cpu=native" cargo instruments -t time --profile=bench --bench realworld --features bench -- --bench hashonly-struuid-rapidhash-v2
7174

7275
# Benchmark WASM targets, which will automatically build the WASM target
73-
RUSTFLAGS="-C target-cpu=native" cargo criterion --bench wasm --all-features
76+
RUSTFLAGS="-C target-cpu=native" cargo criterion --bench wasm --features bench
77+
78+
# Benchmark WASM targets, nightly
79+
RUSTFLAGS="-C target-cpu=native" cargo +nightly criterion --bench wasm --features nightly
7480
```
7581

7682
## Fuzzing
7783
```shell
7884
# fuzz the raw rapidhash method. (assumes cargo-fuzz is installed)
79-
cargo +nightly fuzz run --features unsafe rapidhash
85+
cargo +nightly fuzz run rapidhash
8086

8187
# fuzz the RapidHasher struct with std::hash::Hasher write and finish calls.
82-
cargo +nightly fuzz run --features unsafe rapidhasher
88+
cargo +nightly fuzz run rapidhasher
8389

8490
# use AFL fuzzing. (assumes cargo-afl is installed)
8591
cargo afl fuzz -i in -o out target/debug/afl_rapidhash

docs/generate_charts.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,27 @@ def draw_hash():
6666
("rapidhash_cc_v1", "r"),
6767
]
6868

69-
if "--small" in sys.argv:
69+
if "--small" in sys.argv or "--medium" in sys.argv:
7070
hash_settings = [
7171
("rapidhash-f", "b"),
7272
("foldhash-f", "y"),
73+
# ("fxhash", "r"),
74+
]
75+
76+
if "--small" in sys.argv and "--raw" in sys.argv:
77+
hash_settings = [
78+
("rapidhash_cc_v3", "y"),
79+
("rapidhash_cc_v3_1", "g"),
7380
]
7481

7582
hash_names = [hash_function for hash_function, _ in hash_settings]
7683

7784
# also available: 65536, 524288000
7885
sizes = [2, 8, 16, 25, 50, 64, 80, 160, 256, 350, 1024, 4096, ]
7986
if "--small" in sys.argv:
80-
sizes = list(range(0, 257))
87+
sizes = list(range(0, 300))
88+
if "--medium" in sys.argv: # 50B increments up to 4kB
89+
sizes = list(range(0, 40 * 100, 50))
8190

8291
latency_data = []
8392
throughput_data = []
@@ -92,6 +101,8 @@ def draw_hash():
92101
prefix = "str"
93102
if "--small" in sys.argv:
94103
prefix = "small"
104+
if "--medium" in sys.argv:
105+
prefix = "medium"
95106

96107
for size in sizes:
97108
latency, throughput = load_latest_measurement_file("hash", hash_function, f"{prefix}_{size}")
@@ -102,22 +113,31 @@ def draw_hash():
102113
throughput_data.append(throughput_row)
103114

104115
u64_measurement = "u64"
116+
s64k_measurement = "str_65536"
105117
if "--raw" in sys.argv:
106118
u64_measurement = "str_8"
107119

120+
if "--small" in sys.argv:
121+
u64_measurement = "small_8"
122+
s64k_measurement = "small_256"
123+
124+
if "--medium" in sys.argv:
125+
u64_measurement = "medium_100"
126+
s64k_measurement = "medium_19000"
127+
108128
latency, throughput = load_latest_measurement_file("hash", hash_function, u64_measurement)
109129
latency_data_u64.append(latency)
110130
throughput_data_u64.append(throughput)
111131

112-
latency, throughput = load_latest_measurement_file("hash", hash_function, "str_65536")
132+
latency, throughput = load_latest_measurement_file("hash", hash_function, s64k_measurement)
113133
latency_data_64k.append(latency)
114134
throughput_data_64k.append(throughput)
115135

116136
fig, axs = plt.subplots(2, 2, figsize=(12, 8), dpi=300)
117137

118138
for i, (hash_function, color) in reversed(list(enumerate(hash_settings))):
119139
linestyle = "--" if hash_function.endswith("-f") else "-"
120-
linewidth = 1.0 if "--small" in sys.argv else 0.5
140+
linewidth = 1.0 if "--small" in sys.argv or "--medium" in sys.argv else 0.5
121141

122142
axs[0, 0].plot(sizes, latency_data[i], label=hash_function, color=color, linestyle=linestyle, linewidth=linewidth)
123143
axs[0, 1].plot(sizes, throughput_data[i], label=hash_function, color=color, linestyle=linestyle, linewidth=linewidth)
@@ -137,13 +157,13 @@ def draw_hash():
137157
axs[1, 1].bar(hash_function, throughput_data_64k[i], color=color, edgecolor=edgecolor, hatch=hatchstyle, zorder=3)
138158

139159
labels = sizes
140-
if "--small" in sys.argv:
160+
if "--small" in sys.argv or "--medium" in sys.argv:
141161
labels = sizes[::20]
142162

143163
axs[0, 0].set_title("Latency (byte stream)")
144164
axs[0, 0].set_xlabel("Input size (bytes)")
145165
axs[0, 0].set_ylabel("Latency (ns)")
146-
if "--small" not in sys.argv:
166+
if not ("--small" in sys.argv or "--medium" in sys.argv):
147167
axs[0, 0].set_xscale("log", base=2)
148168
axs[0, 0].set_yscale("log", base=10)
149169
axs[0, 0].set_xticks(labels)
@@ -152,19 +172,29 @@ def draw_hash():
152172
axs[0, 1].set_title("Throughput (byte stream)")
153173
axs[0, 1].set_xlabel("Input size (bytes)")
154174
axs[0, 1].set_ylabel("Throughput (GB/s)")
155-
if "--small" not in sys.argv:
175+
if not ("--small" in sys.argv or "--medium" in sys.argv):
156176
axs[0, 1].set_xscale("log", base=2)
157177
axs[0, 1].set_yscale("log", base=10)
158178
axs[0, 1].set_xticks(labels)
159179
axs[0, 1].set_xticklabels(labels, rotation=90, ha="right")
160180

161-
axs[1, 0].set_title("Throughput (u64)")
181+
if "--small" in sys.argv:
182+
axs[1, 0].set_title("Throughput (bytes, 8B)")
183+
elif "--medium" in sys.argv:
184+
axs[1, 0].set_title("Throughput (bytes, 100B)")
185+
else:
186+
axs[1, 0].set_title("Throughput (u64)")
162187
axs[1, 0].set_ylabel("Throughput (M Items/s)")
163188
axs[1, 0].set_xticks(range(len(hash_names)))
164189
axs[1, 0].set_xticklabels(hash_names, rotation=45, ha="right")
165190
axs[1, 0].grid(True, zorder=0, color="gainsboro")
166191

167-
axs[1, 1].set_title("Throughput (bytes, 64kB)")
192+
if "--small" in sys.argv:
193+
axs[1, 1].set_title("Throughput (bytes, 256B)")
194+
if "--medium" in sys.argv:
195+
axs[1, 1].set_title("Throughput (bytes, 19kB)")
196+
else:
197+
axs[1, 1].set_title("Throughput (bytes, 64kB)")
168198
axs[1, 1].set_ylabel("Throughput (GB/s)")
169199
axs[1, 1].set_xticks(range(len(hash_names)))
170200
axs[1, 1].set_xticklabels(hash_names, rotation=45, ha="right")

0 commit comments

Comments
 (0)