OXYGENHUB
◆ v0.9.0 oxygen / crates / oxy-core / src / fasthash.rs
3.5 KiB · Rust · last changed FastHasher reads whole words with as_chunks 1 month ago source blame history raw
0bdc3f91 Andy McCall ↶ 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
9
fn seed() -> u64 {
10
    static SEED: OnceLock<u64> = OnceLock::new();
11
    *SEED.get_or_init(rand::random::<u64>)
12
}
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 {
19
    fn default() -> FastState {
20
        FastState(seed())
21
    }
22
}
23
24
impl BuildHasher for FastState {
25
    type Hasher = FastHasher;
26
    fn build_hasher(&self) -> FastHasher {
27
        FastHasher(self.0)
28
    }
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]
37
    fn mix(&mut self, word: u64) {
38
        self.0 = (self.0 ^ word)
39
            .wrapping_mul(0x9e37_79b9_7f4a_7c15)
40
            .rotate_left(31);
41
    }
42
}
43
44
impl Hasher for FastHasher {
45
    #[inline]
46
    fn write(&mut self, bytes: &[u8]) {
faa9cd05 Andy McCall ↶ 47
        let (chunks, rest) = bytes.as_chunks::<8>();
48
        for c in chunks {
49
            self.mix(u64::from_le_bytes(*c));
0bdc3f91 Andy McCall ↶ 50
        }
51
        if !rest.is_empty() {
52
            let mut b = [0u8; 8];
53
            b[..rest.len()].copy_from_slice(rest);
54
            self.mix(u64::from_le_bytes(b) ^ ((rest.len() as u64) << 59));
55
        }
56
    }
57
58
    #[inline]
59
    fn write_u8(&mut self, n: u8) {
60
        self.mix(n as u64 | 0x100);
61
    }
62
63
    #[inline]
64
    fn write_u32(&mut self, n: u32) {
65
        self.mix(n as u64 | 0x1_0000_0000);
66
    }
67
68
    #[inline]
69
    fn write_u64(&mut self, n: u64) {
70
        self.mix(n);
71
    }
72
73
    #[inline]
74
    fn write_usize(&mut self, n: usize) {
75
        self.mix(n as u64);
76
    }
77
78
    #[inline]
79
    fn finish(&self) -> u64 {
80
        let mut z = self.0;
81
        z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
82
        z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
83
        z ^ (z >> 31)
84
    }
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
95
    fn h<T: Hash + ?Sized>(state: &FastState, v: &T) -> u64 {
96
        state.hash_one(v)
97
    }
98
99
    #[test]
100
    fn distinct_keys_spread_and_equal_keys_agree() {
101
        let s = FastState(42);
102
        let a = ([7u8; 32], 1u32);
103
        assert_eq!(h(&s, &a), h(&s, &a));
104
        assert_ne!(h(&s, &a), h(&s, &([7u8; 32], 2u32)), "the index matters");
105
        assert_ne!(
106
            h(&s, &[1u8, 2, 3][..]),
107
            h(&s, &[1u8, 2, 3, 0][..]),
108
            "length matters"
109
        );
110
        // Consecutive indices of one change land in different buckets of a small table.
111
        let buckets: std::collections::HashSet<u64> =
112
            (0..64u32).map(|i| h(&s, &([9u8; 32], i)) & 63).collect();
113
        assert!(buckets.len() > 32, "{} of 64 buckets used", buckets.len());
114
        assert_ne!(
115
            h(&FastState(1), &a),
116
            h(&FastState(2), &a),
117
            "the seed matters"
118
        );
119
    }
120
121
    #[test]
122
    fn maps_work() {
123
        let mut m: FastMap<([u8; 32], u32), u32> = FastMap::default();
124
        for i in 0..10_000u32 {
125
            m.insert(([(i % 251) as u8; 32], i), i);
126
        }
127
        assert_eq!(m.len(), 10_000);
128
        assert_eq!(m[&([77u8; 32], 77)], 77);
129
    }
130
}