OXYGENHUB

Coverage / crates/oxy-core/src/fasthash.rs 91.4%

64 of 70 lines run, 21 of 25 functions · run #358 on main · run missed

1
//! A fast hasher for the in-memory graphs (spec §25): their keys begin with
2
//! BLAKE3 hashes, so the bytes are already uniform and SipHash's cost buys
3
//! nothing. A per-process random seed keeps bucket placement unpredictable,
4
//! so keys chosen by someone else cannot be aimed at one bucket.
5
6
use std::hash::{BuildHasher, Hasher};
7
use std::sync::OnceLock;
8
9224793×
fn seed() -> u64 {
10
    static SEED: OnceLock<u64> = OnceLock::new();
11224793×
    *SEED.get_or_init(rand::random::<u64>)
12224793×
}
13
14
/// Builds [`FastHasher`]s with the process seed.
15
#[derive(Clone, Copy, Debug)]
16
pub struct FastState(u64);
17
18
impl Default for FastState {
19224793×
    fn default() -> FastState {
20224793×
        FastState(seed())
21224793×
    }
22
}
23
24
impl BuildHasher for FastState {
25
    type Hasher = FastHasher;
2663185690×
    fn build_hasher(&self) -> FastHasher {
2763185690×
        FastHasher(self.0)
2863185690×
    }
29
}
30
31
/// Folds each 8-byte word into the state with a multiply and rotate, then
32
/// finishes with SplitMix64's mixer so every input bit reaches every output bit.
33
pub struct FastHasher(u64);
34
35
impl FastHasher {
36
    #[inline]
37306150357×
    fn mix(&mut self, word: u64) {
38306150357×
        self.0 = (self.0 ^ word)
39306150357×
            .wrapping_mul(0x9e37_79b9_7f4a_7c15)
40306150357×
            .rotate_left(31);
41306150357×
    }
42
}
43
44
impl Hasher for FastHasher {
45
    #[inline]
4662930116×
    fn write(&mut self, bytes: &[u8]) {
4762930116×
        let (chunks, rest) = bytes.as_chunks::<8>();
48208501894×
        for c in chunks {
49208501894×
            self.mix(u64::from_le_bytes(*c));
50208501894×
        }
5162930116×
        if !rest.is_empty() {
522×
            let mut b = [0u8; 8];
532×
            b[..rest.len()].copy_from_slice(rest);
542×
            self.mix(u64::from_le_bytes(b) ^ ((rest.len() as u64) << 59));
5562930114×
        }
5662930116×
    }
57
58
    #[inline]
590×
    fn write_u8(&mut self, n: u8) {
600×
        self.mix(n as u64 | 0x100);
610×
    }
62
63
    #[inline]
6431457524×
    fn write_u32(&mut self, n: u32) {
6531457524×
        self.mix(n as u64 | 0x1_0000_0000);
6631457524×
    }
67
68
    #[inline]
690×
    fn write_u64(&mut self, n: u64) {
700×
        self.mix(n);
710×
    }
72
73
    #[inline]
7466190937×
    fn write_usize(&mut self, n: usize) {
7566190937×
        self.mix(n as u64);
7666190937×
    }
77
78
    #[inline]
7963185690×
    fn finish(&self) -> u64 {
8063185690×
        let mut z = self.0;
8163185690×
        z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
8263185690×
        z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
8363185690×
        z ^ (z >> 31)
8463185690×
    }
85
}
86
87
pub type FastMap<K, V> = std::collections::HashMap<K, V, FastState>;
88
pub type FastSet<K> = std::collections::HashSet<K, FastState>;
89
90
#[cfg(test)]
91
mod tests {
92
    use super::*;
93
    use std::hash::Hash;
94
9572×
    fn h<T: Hash + ?Sized>(state: &FastState, v: &T) -> u64 {
9672×
        state.hash_one(v)
9772×
    }
98
99
    #[test]
1001×
    fn distinct_keys_spread_and_equal_keys_agree() {
1011×
        let s = FastState(42);
1021×
        let a = ([7u8; 32], 1u32);
1031×
        assert_eq!(h(&s, &a), h(&s, &a));
1041×
        assert_ne!(h(&s, &a), h(&s, &([7u8; 32], 2u32)), "the index matters");
1051×
        assert_ne!(
1061×
            h(&s, &[1u8, 2, 3][..]),
1071×
            h(&s, &[1u8, 2, 3, 0][..]),
108
            "length matters"
109
        );
110
        // Consecutive indices of one change land in different buckets of a small table.
1111×
        let buckets: std::collections::HashSet<u64> =
11264×
            (0..64u32).map(|i| h(&s, &([9u8; 32], i)) & 63).collect();
1131×
        assert!(buckets.len() > 32, "{} of 64 buckets used", buckets.len());
1141×
        assert_ne!(
1151×
            h(&FastState(1), &a),
1161×
            h(&FastState(2), &a),
117
            "the seed matters"
118
        );
1191×
    }
120
121
    #[test]
1221×
    fn maps_work() {
1231×
        let mut m: FastMap<([u8; 32], u32), u32> = FastMap::default();
12410000×
        for i in 0..10_000u32 {
12510000×
            m.insert(([(i % 251) as u8; 32], i), i);
12610000×
        }
1271×
        assert_eq!(m.len(), 10_000);
1281×
        assert_eq!(m[&([77u8; 32], 77)], 77);
1291×
    }
130
}