Skip to content

Commit 94ccebd

Browse files
authored
hashes: reduce RapidHash collection hashing overhead
Keep deterministic and randomized byte hashing out of fixed-size key call sites so collections avoid inlining both V3 schedules. Replace the one-entry lookup microbenchmark with representative 4,096-key operations.
1 parent 0ac6f92 commit 94ccebd

3 files changed

Lines changed: 151 additions & 17 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"rscrypto" = "patch"
3+
---
4+
5+
Reduce RapidHash overhead for fixed-size collection keys without changing hash outputs or allocation behavior.

benches/rapidhash.rs

Lines changed: 131 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,34 @@ use core::{
88
};
99
use std::collections::HashMap;
1010

11-
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
11+
use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
12+
13+
const COLLECTION_KEYS: usize = 4096;
14+
15+
fn collection_key(index: u64) -> [u8; 32] {
16+
let mut state = index;
17+
let mut key = [0u8; 32];
18+
for lane in key.chunks_exact_mut(8) {
19+
state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
20+
let mut word = state;
21+
word = (word ^ (word >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
22+
word = (word ^ (word >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
23+
lane.copy_from_slice(&(word ^ (word >> 31)).to_le_bytes());
24+
}
25+
key
26+
}
27+
28+
fn collection_keys(start: u64) -> Vec<[u8; 32]> {
29+
(0..COLLECTION_KEYS)
30+
.map(|index| collection_key(start.wrapping_add(index as u64)))
31+
.collect()
32+
}
33+
34+
fn populated_map<S: BuildHasher>(keys: &[[u8; 32]], state: S) -> HashMap<[u8; 32], usize, S> {
35+
let mut map = HashMap::with_capacity_and_hasher(keys.len(), state);
36+
map.extend(keys.iter().copied().enumerate().map(|(value, key)| (key, value)));
37+
map
38+
}
1239

1340
fn rapidhash_v3_64(c: &mut Criterion) {
1441
let inputs = common::comp_sizes();
@@ -48,21 +75,110 @@ fn rapidhash_seeded_state(c: &mut Criterion) {
4875
group.finish();
4976
}
5077

51-
fn rapidhash_hashmap_lookup(c: &mut Criterion) {
52-
let key = common::random_bytes(32);
53-
let mut ours = HashMap::with_capacity_and_hasher(1, rscrypto::RapidSeededState::new(0));
54-
let mut upstream = HashMap::with_capacity_and_hasher(1, rapidhash::quality::SeedableState::fixed());
55-
ours.insert(key.as_slice(), 1u8);
56-
upstream.insert(key.as_slice(), 1u8);
78+
fn rapidhash_key_types(c: &mut Criterion) {
79+
let ours = rscrypto::RapidSeededState::new(0);
80+
let upstream = rapidhash::quality::SeedableState::fixed();
81+
let integer = 0xa5c3_17e9_6b4d_2f01u64;
82+
let bytes = collection_key(17);
83+
let text = "collection-key";
84+
85+
let mut integer_group = c.benchmark_group("rapidhash-hash-one/u64");
86+
integer_group.bench_function("rscrypto", |b| b.iter(|| black_box(ours.hash_one(black_box(integer)))));
87+
integer_group.bench_function("rapidhash", |b| {
88+
b.iter(|| black_box(upstream.hash_one(black_box(integer))))
89+
});
90+
integer_group.finish();
5791

58-
let mut group = c.benchmark_group("rapidhash-hashmap/lookup-32");
59-
group.bench_function("rscrypto", |b| {
60-
b.iter(|| black_box(ours.get(black_box(key.as_slice()))))
92+
let mut bytes_group = c.benchmark_group("rapidhash-hash-one/array-32");
93+
bytes_group.bench_function("rscrypto", |b| b.iter(|| black_box(ours.hash_one(black_box(bytes)))));
94+
bytes_group.bench_function("rapidhash", |b| {
95+
b.iter(|| black_box(upstream.hash_one(black_box(bytes))))
6196
});
62-
group.bench_function("rapidhash", |b| {
63-
b.iter(|| black_box(upstream.get(black_box(key.as_slice()))))
97+
bytes_group.finish();
98+
99+
let mut text_group = c.benchmark_group("rapidhash-hash-one/str-14");
100+
text_group.bench_function("rscrypto", |b| b.iter(|| black_box(ours.hash_one(black_box(text)))));
101+
text_group.bench_function("rapidhash", |b| {
102+
b.iter(|| black_box(upstream.hash_one(black_box(text))))
64103
});
65-
group.finish();
104+
text_group.finish();
105+
}
106+
107+
fn rapidhash_hashmap_operations(c: &mut Criterion) {
108+
let present = collection_keys(0);
109+
let absent = collection_keys(COLLECTION_KEYS as u64);
110+
let ours = populated_map(&present, rscrypto::RapidSeededState::new(0));
111+
let upstream = populated_map(&present, rapidhash::quality::SeedableState::fixed());
112+
113+
let mut insert = c.benchmark_group("rapidhash-hashmap/insert-32");
114+
insert.throughput(Throughput::Elements(COLLECTION_KEYS as u64));
115+
insert.bench_function("rscrypto", |b| {
116+
b.iter_batched_ref(
117+
|| HashMap::with_capacity_and_hasher(COLLECTION_KEYS, rscrypto::RapidSeededState::new(0)),
118+
|map| {
119+
for (value, key) in black_box(present.as_slice()).iter().copied().enumerate() {
120+
black_box(map.insert(key, value));
121+
}
122+
},
123+
BatchSize::LargeInput,
124+
)
125+
});
126+
insert.bench_function("rapidhash", |b| {
127+
b.iter_batched_ref(
128+
|| HashMap::with_capacity_and_hasher(COLLECTION_KEYS, rapidhash::quality::SeedableState::fixed()),
129+
|map| {
130+
for (value, key) in black_box(present.as_slice()).iter().copied().enumerate() {
131+
black_box(map.insert(key, value));
132+
}
133+
},
134+
BatchSize::LargeInput,
135+
)
136+
});
137+
insert.finish();
138+
139+
let mut hit = c.benchmark_group("rapidhash-hashmap/hit-32");
140+
hit.throughput(Throughput::Elements(COLLECTION_KEYS as u64));
141+
hit.bench_function("rscrypto", |b| {
142+
b.iter(|| {
143+
let mut found = 0usize;
144+
for key in black_box(present.as_slice()) {
145+
found = found.wrapping_add(black_box(ours.get(black_box(key))).is_some() as usize);
146+
}
147+
black_box(found)
148+
})
149+
});
150+
hit.bench_function("rapidhash", |b| {
151+
b.iter(|| {
152+
let mut found = 0usize;
153+
for key in black_box(present.as_slice()) {
154+
found = found.wrapping_add(black_box(upstream.get(black_box(key))).is_some() as usize);
155+
}
156+
black_box(found)
157+
})
158+
});
159+
hit.finish();
160+
161+
let mut miss = c.benchmark_group("rapidhash-hashmap/miss-32");
162+
miss.throughput(Throughput::Elements(COLLECTION_KEYS as u64));
163+
miss.bench_function("rscrypto", |b| {
164+
b.iter(|| {
165+
let mut found = 0usize;
166+
for key in black_box(absent.as_slice()) {
167+
found = found.wrapping_add(black_box(ours.get(black_box(key))).is_some() as usize);
168+
}
169+
black_box(found)
170+
})
171+
});
172+
miss.bench_function("rapidhash", |b| {
173+
b.iter(|| {
174+
let mut found = 0usize;
175+
for key in black_box(absent.as_slice()) {
176+
found = found.wrapping_add(black_box(upstream.get(black_box(key))).is_some() as usize);
177+
}
178+
black_box(found)
179+
})
180+
});
181+
miss.finish();
66182
}
67183

68184
fn rapidhash_streaming(c: &mut Criterion) {
@@ -113,7 +229,8 @@ criterion_group!(
113229
benches,
114230
rapidhash_v3_64,
115231
rapidhash_seeded_state,
116-
rapidhash_hashmap_lookup,
232+
rapidhash_key_types,
233+
rapidhash_hashmap_operations,
117234
rapidhash_streaming
118235
);
119236
criterion_main!(benches);

src/hashes/fast/rapidhash/stream.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,19 @@ impl core::fmt::Debug for RapidHasher {
270270
}
271271
}
272272

273+
// Keep byte hashing out of fixed-size key callers; inlining either schedule bloats collection
274+
// paths.
275+
#[inline(never)]
276+
fn hash_randomized(bytes: &[u8], seed: u64, random_word0: u64) -> u64 {
277+
let state = RapidSecrets::derived(seed, random_word0);
278+
rapidhash_core(bytes, state.seed, &state.words)
279+
}
280+
281+
#[inline(never)]
282+
fn hash_deterministic(bytes: &[u8], seed: u64) -> u64 {
283+
rapidhash_core(bytes, seed, &DEFAULT_SECRETS)
284+
}
285+
273286
macro_rules! write_integer {
274287
($($method:ident, $ty:ty, $unsigned:ty),+ $(,)?) => {
275288
$(
@@ -299,10 +312,9 @@ impl Hasher for RapidHasher {
299312
}
300313
self.flush_sponge();
301314
self.seed = if self.random_word0 == 0 {
302-
rapidhash_core(bytes, self.seed, &DEFAULT_SECRETS)
315+
hash_deterministic(bytes, self.seed)
303316
} else {
304-
let state = RapidSecrets::derived(self.seed, self.random_word0);
305-
rapidhash_core(bytes, state.seed, &state.words)
317+
hash_randomized(bytes, self.seed, self.random_word0)
306318
};
307319
}
308320

0 commit comments

Comments
 (0)