Skip to content
This repository was archived by the owner on Sep 22, 2025. It is now read-only.

Commit f792142

Browse files
committed
Issue 210: Fast path hardening take 2 (#215)
* Reverts the basic fast path for u64 to be two folded multiplies. Signed-off-by: Tom Kaitchuck <[email protected]>
1 parent 545a200 commit f792142

File tree

9 files changed

+56
-21
lines changed

9 files changed

+56
-21
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ rand = "0.8.5"
100100
pcg-mwc = "0.2.1"
101101
serde_json = "1.0.59"
102102
hashbrown = "0.14.3"
103+
smallvec = "1.13.1"
103104

104105
[package.metadata.docs.rs]
105106
rustc-args = ["-C", "target-feature=+aes"]

compare/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ codegen-units = 1
2929

3030
[dependencies]
3131
ahash = { path = "../", default-features = false }
32+
pcg-mwc = "0.2.1"
33+
rand = "0.8.5"
34+
rand_core = "0.6.4"
3235

3336
[dev-dependencies]
3437
criterion = "0.3.3"

compare/src/main.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use std::io::Error;
2+
use std::fs::File;
3+
use std::io::Write;
4+
use pcg_mwc::Mwc256XXA64;
5+
use ahash::RandomState;
6+
use std::io::BufWriter;
7+
use std::path::Path;
8+
use rand_core::SeedableRng;
9+
use rand::Rng;
10+
use std::time::Instant;
11+
12+
13+
fn main() -> Result<(), Error> {
14+
let mut r = Mwc256XXA64::seed_from_u64(0xe786_c22b_119c_1479);
15+
16+
let path = Path::new("hash_output");
17+
18+
let mut file = BufWriter::new(File::create(path)?);
19+
let hasher = RandomState::with_seeds(r.gen(), r.gen(), r.gen(), r.gen());
20+
let start = Instant::now();
21+
let mut sum: u64 = 0;
22+
for i in 0..i32::MAX {
23+
let value = hasher.hash_one(i as u64);
24+
sum = sum.wrapping_add(value);
25+
let value: [u8; 8] = value.to_ne_bytes();
26+
file.write_all(&value)?;
27+
}
28+
let elapsed = start.elapsed();
29+
println!("Sum {} Elapsed time: {}", sum, elapsed.as_millis());
30+
file.flush()?;
31+
Ok(())
32+
}

src/aes_hash.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,7 @@ pub(crate) struct AHasherU64 {
225225
impl Hasher for AHasherU64 {
226226
#[inline]
227227
fn finish(&self) -> u64 {
228-
let rot = (self.pad & 63) as u32;
229-
self.buffer.rotate_left(rot)
228+
folded_multiply(self.buffer, self.pad)
230229
}
231230

232231
#[inline]
@@ -252,7 +251,6 @@ impl Hasher for AHasherU64 {
252251
#[inline]
253252
fn write_u64(&mut self, i: u64) {
254253
self.buffer = folded_multiply(i ^ self.buffer, MULTIPLE);
255-
self.pad = self.pad.wrapping_add(i);
256254
}
257255

258256
#[inline]

src/fallback_hash.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ impl AHasher {
5656
#[allow(dead_code)] // Is not called if non-fallback hash is used.
5757
pub(crate) fn from_random_state(rand_state: &RandomState) -> AHasher {
5858
AHasher {
59-
buffer: rand_state.k0,
60-
pad: rand_state.k1,
59+
buffer: rand_state.k1,
60+
pad: rand_state.k0,
6161
extra_keys: [rand_state.k2, rand_state.k3],
6262
}
6363
}
@@ -117,7 +117,7 @@ impl AHasher {
117117
#[inline]
118118
#[cfg(feature = "specialize")]
119119
fn short_finish(&self) -> u64 {
120-
self.buffer.wrapping_add(self.pad)
120+
folded_multiply(self.buffer, self.pad)
121121
}
122122
}
123123

@@ -210,8 +210,8 @@ pub(crate) struct AHasherU64 {
210210
impl Hasher for AHasherU64 {
211211
#[inline]
212212
fn finish(&self) -> u64 {
213-
let rot = (self.pad & 63) as u32;
214-
self.buffer.rotate_left(rot)
213+
folded_multiply(self.buffer, self.pad)
214+
//self.buffer
215215
}
216216

217217
#[inline]
@@ -237,7 +237,6 @@ impl Hasher for AHasherU64 {
237237
#[inline]
238238
fn write_u64(&mut self, i: u64) {
239239
self.buffer = folded_multiply(i ^ self.buffer, MULTIPLE);
240-
self.pad = self.pad.wrapping_add(i);
241240
}
242241

243242
#[inline]

src/hash_quality_test.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -338,22 +338,24 @@ fn test_length_extension<T: Hasher>(hasher: impl Fn(u128, u128) -> T) {
338338
}
339339

340340
fn test_sparse<T: Hasher>(hasher: impl Fn() -> T) {
341+
use smallvec::SmallVec;
342+
341343
let mut buf = [0u8; 256];
342344
let mut hashes = HashMap::new();
343-
for idx_1 in 0..256 {
344-
for idx_2 in idx_1 + 1..256 {
345+
for idx_1 in 0..255_u8 {
346+
for idx_2 in idx_1 + 1..=255_u8 {
345347
for value_1 in [1, 2, 4, 8, 16, 32, 64, 128] {
346348
for value_2 in [
347349
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 16, 17, 18, 20, 24, 31, 32, 33, 48, 64, 96, 127, 128, 129,
348350
192, 254, 255,
349351
] {
350-
buf[idx_1] = value_1;
351-
buf[idx_2] = value_2;
352+
buf[idx_1 as usize] = value_1;
353+
buf[idx_2 as usize] = value_2;
352354
let hash_value = hash_with(&buf, &mut hasher());
353-
let keys = hashes.entry(hash_value).or_insert(Vec::new());
354-
keys.push((idx_1, value_1, idx_2, value_2));
355-
buf[idx_1] = 0;
356-
buf[idx_2] = 0;
355+
let keys = hashes.entry(hash_value).or_insert(SmallVec::<[[u8; 4]; 1]>::new());
356+
keys.push([idx_1, value_1, idx_2, value_2]);
357+
buf[idx_1 as usize] = 0;
358+
buf[idx_2 as usize] = 0;
357359
}
358360
}
359361
}

src/random_state.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -470,8 +470,8 @@ impl BuildHasherExt for RandomState {
470470
#[inline]
471471
fn hash_as_u64<T: Hash + ?Sized>(&self, value: &T) -> u64 {
472472
let mut hasher = AHasherU64 {
473-
buffer: self.k0,
474-
pad: self.k1,
473+
buffer: self.k1,
474+
pad: self.k0,
475475
};
476476
value.hash(&mut hasher);
477477
hasher.finish()

tests/bench.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ fn gen_strings() -> Vec<String> {
7575
macro_rules! bench_inputs {
7676
($group:ident, $hash:ident) => {
7777
// Number of iterations per batch should be high enough to hide timing overhead.
78-
let size = BatchSize::NumIterations(2_000);
78+
let size = BatchSize::NumIterations(50_000);
7979

8080
let mut rng = rand::thread_rng();
8181
$group.bench_function("u8", |b| b.iter_batched(|| rng.gen::<u8>(), |v| $hash(&v), size));

tests/map_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ fn test_byte_dist() {
238238
let mut table: [bool; 256 * 8] = [false; 256 * 8];
239239
let hasher = RandomState::with_seeds(r.gen(), r.gen(), r.gen(), r.gen());
240240
for i in 0..128 {
241-
let mut keys: [u8; 8] = hasher.hash_one(i as u64).to_ne_bytes();
241+
let mut keys: [u8; 8] = hasher.hash_one((i as u64) << 30).to_ne_bytes();
242242
//let mut keys = r.next_u64().to_ne_bytes(); //This is a control to test assert sensitivity.
243243
for idx in 0..8 {
244244
while table[idx * 256 + keys[idx] as usize] {

0 commit comments

Comments
 (0)