/home/gh-runner/action-runner3/_work/feoxdb/feoxdb/src/utils/hash.rs
Line | Count | Source |
1 | | use std::hash::Hasher; |
2 | | |
3 | | const C1: u32 = 0xcc9e2d51; |
4 | | const C2: u32 = 0x1b873593; |
5 | | const R1: u32 = 15; |
6 | | const R2: u32 = 13; |
7 | | const M: u32 = 5; |
8 | | const N: u32 = 0xe6546b64; |
9 | | |
10 | | #[inline(always)] |
11 | 12.3k | pub fn murmur3_32(key: &[u8], seed: u32) -> u32 { |
12 | 12.3k | let mut h = seed; |
13 | 12.3k | let mut chunks = key.chunks_exact(4); |
14 | | |
15 | 25.3k | for chunk in chunks12.3k .by_ref12.3k () { |
16 | 25.3k | let mut k = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]); |
17 | 25.3k | k = k.wrapping_mul(C1); |
18 | 25.3k | k = k.rotate_left(R1); |
19 | 25.3k | k = k.wrapping_mul(C2); |
20 | 25.3k | |
21 | 25.3k | h ^= k; |
22 | 25.3k | h = h.rotate_left(R2); |
23 | 25.3k | h = h.wrapping_mul(M).wrapping_add(N); |
24 | 25.3k | } |
25 | | |
26 | 12.3k | let remainder = chunks.remainder(); |
27 | 12.3k | if !remainder.is_empty() { |
28 | 3.09k | let mut k = 0u32; |
29 | 5.33k | for (i, &byte) in remainder3.09k .iter3.09k ().enumerate3.09k () { |
30 | 5.33k | k |= (byte as u32) << (i * 8); |
31 | 5.33k | } |
32 | | |
33 | 3.09k | k = k.wrapping_mul(C1); |
34 | 3.09k | k = k.rotate_left(R1); |
35 | 3.09k | k = k.wrapping_mul(C2); |
36 | 3.09k | h ^= k; |
37 | 9.23k | } |
38 | | |
39 | 12.3k | h ^= key.len() as u32; |
40 | 12.3k | h ^= h >> 16; |
41 | 12.3k | h = h.wrapping_mul(0x85ebca6b); |
42 | 12.3k | h ^= h >> 13; |
43 | 12.3k | h = h.wrapping_mul(0xc2b2ae35); |
44 | 12.3k | h ^= h >> 16; |
45 | | |
46 | 12.3k | h |
47 | 12.3k | } |
48 | | |
49 | | /// Compute hash of a key using the best available method. |
50 | | /// |
51 | | /// On x86_64 CPUs with AES-NI support, uses hardware-accelerated hashing. |
52 | | /// Otherwise falls back to MurmurHash3. |
53 | 1.01k | pub fn hash_key(key: &[u8]) -> u32 { |
54 | | #[cfg(target_arch = "x86_64")] |
55 | | { |
56 | 1.01k | if let Some(hash) = simd::hash_key_aes_safe(key) { |
57 | 1.01k | return hash; |
58 | 0 | } |
59 | | } |
60 | 0 | murmur3_32(key, 0) |
61 | 1.01k | } |
62 | | |
63 | | #[cfg(target_arch = "x86_64")] |
64 | | pub mod simd { |
65 | | use std::arch::x86_64::*; |
66 | | |
67 | | /// Compute hash using AES-NI instructions for better performance. |
68 | | /// |
69 | | /// Returns None if AES-NI is not supported on this CPU. |
70 | 1.01k | pub fn hash_key_aes_safe(key: &[u8]) -> Option<u32> { |
71 | 1.01k | if is_x86_feature_detected!("aes") { |
72 | 1.01k | unsafe { Some(hash_key_aes_unsafe(key)) } |
73 | | } else { |
74 | 0 | None |
75 | | } |
76 | 1.01k | } |
77 | | |
78 | | /// Compute hash using AES-NI instructions for better performance. |
79 | | /// |
80 | | /// # Safety |
81 | | /// |
82 | | /// This function requires the CPU to support AES-NI instructions. |
83 | | /// The caller must ensure that the CPU supports AES-NI before calling this function. |
84 | | /// This is typically checked using `is_x86_feature_detected!("aes")`. |
85 | | #[target_feature(enable = "aes")] |
86 | | #[inline] |
87 | 1.01k | unsafe fn hash_key_aes_unsafe(key: &[u8]) -> u32 { |
88 | | // Use a non-zero initial seed for better distribution |
89 | 1.01k | let seed = _mm_set_epi32( |
90 | 1.01k | 0x9747b28c_u32 as i32, |
91 | 1.01k | 0xc2b2ae35_u32 as i32, |
92 | 1.01k | 0x85ebca6b_u32 as i32, |
93 | 1.01k | 0xe6546b64_u32 as i32, |
94 | | ); |
95 | 1.01k | let mut hash = seed; |
96 | | |
97 | | // Process full 16-byte chunks |
98 | 1.01k | let mut chunks = key.chunks_exact(16); |
99 | 1.01k | for chunk0 in chunks.by_ref() { |
100 | 0 | let data = _mm_loadu_si128(chunk.as_ptr() as *const __m128i); |
101 | 0 | // Mix the data with XOR before AES round |
102 | 0 | hash = _mm_xor_si128(hash, data); |
103 | 0 | // Apply AES round |
104 | 0 | hash = _mm_aesenc_si128(hash, seed); |
105 | 0 | } |
106 | | |
107 | | // Handle remainder |
108 | 1.01k | let remainder = chunks.remainder(); |
109 | 1.01k | if !remainder.is_empty() { |
110 | 1.01k | let mut buffer = [0u8; 16]; |
111 | 1.01k | buffer[..remainder.len()].copy_from_slice(remainder); |
112 | 1.01k | // Mix in the length of remainder for better distribution |
113 | 1.01k | buffer[15] = remainder.len() as u8; |
114 | 1.01k | |
115 | 1.01k | let data = _mm_loadu_si128(buffer.as_ptr() as *const __m128i); |
116 | 1.01k | hash = _mm_xor_si128(hash, data); |
117 | 1.01k | hash = _mm_aesenc_si128(hash, seed); |
118 | 1.01k | }0 |
119 | | |
120 | | // Final mixing: incorporate key length |
121 | 1.01k | let len_vec = _mm_set1_epi32(key.len() as i32); |
122 | 1.01k | hash = _mm_xor_si128(hash, len_vec); |
123 | 1.01k | hash = _mm_aesenc_si128(hash, seed); |
124 | | |
125 | | // Extract and mix all 4 32-bit parts for better distribution |
126 | 1.01k | let a = _mm_extract_epi32(hash, 0) as u32; |
127 | 1.01k | let b = _mm_extract_epi32(hash, 1) as u32; |
128 | 1.01k | let c = _mm_extract_epi32(hash, 2) as u32; |
129 | 1.01k | let d = _mm_extract_epi32(hash, 3) as u32; |
130 | | |
131 | | // Final mixing similar to MurmurHash3 finalizer |
132 | 1.01k | let mut h = a ^ b ^ c ^ d; |
133 | 1.01k | h ^= h >> 16; |
134 | 1.01k | h = h.wrapping_mul(0x85ebca6b); |
135 | 1.01k | h ^= h >> 13; |
136 | 1.01k | h = h.wrapping_mul(0xc2b2ae35); |
137 | 1.01k | h ^= h >> 16; |
138 | | |
139 | 1.01k | h |
140 | 1.01k | } |
141 | | } |
142 | | |
143 | | pub struct MurmurHasher { |
144 | | state: u32, |
145 | | } |
146 | | |
147 | | impl Default for MurmurHasher { |
148 | 1 | fn default() -> Self { |
149 | 1 | Self::new() |
150 | 1 | } |
151 | | } |
152 | | |
153 | | impl MurmurHasher { |
154 | 7 | pub fn new() -> Self { |
155 | 7 | Self { state: 0 } |
156 | 7 | } |
157 | | |
158 | 3 | pub fn with_seed(seed: u32) -> Self { |
159 | 3 | Self { state: seed } |
160 | 3 | } |
161 | | } |
162 | | |
163 | | impl Hasher for MurmurHasher { |
164 | 12 | fn write(&mut self, bytes: &[u8]) { |
165 | 12 | self.state = murmur3_32(bytes, self.state); |
166 | 12 | } |
167 | | |
168 | 10 | fn finish(&self) -> u64 { |
169 | 10 | self.state as u64 |
170 | 10 | } |
171 | | } |