polars_utils/
bloom_filter.rs1const SALT: [u32; 8] = [
6 1203114875, 1150766481, 2284105051, 2729912477, 1884591559, 770785867, 2667333959, 1550580529,
7];
8
9const BLOCK_BYTES: usize = 32;
10
11#[inline]
13fn block_index(hash: u64, num_blocks: usize) -> usize {
14 (((hash >> 32) * num_blocks as u64) >> 32) as usize
15}
16
17#[inline]
18fn block_mask(hash: u64) -> [u32; 8] {
19 let key = hash as u32;
20 std::array::from_fn(|i| 1u32 << (key.wrapping_mul(SALT[i]) >> 27))
21}
22
23#[inline]
24fn load_block(bytes: &[u8; BLOCK_BYTES]) -> [u32; 8] {
25 let words: [u32; 8] = unsafe { std::ptr::read_unaligned(bytes.as_ptr().cast()) };
27 words.map(u32::from_le)
28}
29
30#[inline]
31fn store_block(block: [u32; 8], bytes: &mut [u8; BLOCK_BYTES]) {
32 unsafe { std::ptr::write_unaligned(bytes.as_mut_ptr().cast(), block.map(u32::to_le)) }
34}
35
36#[inline]
39fn block_offset(bitset: &[u8], hash: u64) -> usize {
40 let num_blocks = bitset.len() / BLOCK_BYTES;
41 assert!(num_blocks > 0);
42 block_index(hash, num_blocks) * BLOCK_BYTES
43}
44
45#[inline]
46fn block_bytes(bitset: &[u8], hash: u64) -> &[u8; BLOCK_BYTES] {
47 let offset = block_offset(bitset, hash);
48 unsafe { &*(bitset.as_ptr().add(offset) as *const [u8; BLOCK_BYTES]) }
50}
51
52#[inline]
53fn block_bytes_mut(bitset: &mut [u8], hash: u64) -> &mut [u8; BLOCK_BYTES] {
54 let offset = block_offset(bitset, hash);
55 unsafe { &mut *(bitset.as_mut_ptr().add(offset) as *mut [u8; BLOCK_BYTES]) }
57}
58
59#[inline]
60fn block_contains(block: [u32; 8], mask: [u32; 8]) -> bool {
61 let mut found = true;
62 for i in 0..8 {
63 found &= block[i] & mask[i] != 0;
64 }
65 found
66}
67
68pub fn is_in_set(bitset: &[u8], hash: u64) -> bool {
70 let block = load_block(block_bytes(bitset, hash));
71 block_contains(block, block_mask(hash))
72}
73
74pub fn insert(bitset: &mut [u8], hash: u64) {
76 let bytes = block_bytes_mut(bitset, hash);
77 let mut block = load_block(bytes);
78 let mask = block_mask(hash);
79 for i in 0..8 {
80 block[i] |= mask[i];
81 }
82 store_block(block, bytes);
83}
84
85#[derive(Clone)]
87pub struct SplitBlockBloom {
88 blocks: Vec<[u32; 8]>,
89}
90
91impl SplitBlockBloom {
92 fn num_blocks_for(num_keys: usize, bits_per_key: usize) -> usize {
93 num_keys
94 .saturating_mul(bits_per_key)
95 .div_ceil(BLOCK_BYTES * 8)
96 .max(1)
97 .next_power_of_two()
98 }
99
100 pub fn size_for(num_keys: usize, bits_per_key: usize) -> usize {
102 Self::num_blocks_for(num_keys, bits_per_key).saturating_mul(BLOCK_BYTES)
103 }
104
105 pub fn with_capacity(num_keys: usize, bits_per_key: usize) -> Self {
108 Self {
109 blocks: vec![[0; 8]; Self::num_blocks_for(num_keys, bits_per_key)],
110 }
111 }
112
113 pub fn size_bytes(&self) -> usize {
114 self.blocks.len() * BLOCK_BYTES
115 }
116
117 pub fn num_bits(&self) -> usize {
119 self.size_bytes() * 8
120 }
121
122 #[inline]
123 pub fn insert(&mut self, hash: u64) {
124 let b = block_index(hash, self.blocks.len());
125 let block = unsafe { self.blocks.get_unchecked_mut(b) };
127 let mask = block_mask(hash);
128 for i in 0..8 {
129 block[i] |= mask[i];
130 }
131 }
132
133 #[inline]
134 pub fn contains(&self, hash: u64) -> bool {
135 let b = block_index(hash, self.blocks.len());
136 let block = unsafe { *self.blocks.get_unchecked(b) };
138 block_contains(block, block_mask(hash))
139 }
140
141 pub fn union_with(&mut self, other: &Self) {
143 assert_eq!(self.blocks.len(), other.blocks.len());
144 for (a, b) in self.blocks.iter_mut().zip(&other.blocks) {
145 for i in 0..8 {
146 a[i] |= b[i];
147 }
148 }
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn struct_matches_bitset() {
158 let mut bloom = SplitBlockBloom::with_capacity(1000, 8);
159 let mut bitset = vec![0u8; bloom.size_bytes()];
160 let hashes: Vec<u64> = (0..1000u64)
161 .map(|i| i.wrapping_mul(0x9E3779B97F4A7C15))
162 .collect();
163 for &h in &hashes {
164 bloom.insert(h);
165 insert(&mut bitset, h);
166 }
167 for h in hashes
168 .iter()
169 .copied()
170 .chain((1000..2000u64).map(|i| i.wrapping_mul(0x9E3779B97F4A7C15)))
171 {
172 assert_eq!(bloom.contains(h), is_in_set(&bitset, h));
173 }
174 assert!(hashes.iter().all(|&h| bloom.contains(h)));
175 }
176
177 #[test]
178 #[should_panic]
179 fn is_in_set_needs_a_block() {
180 is_in_set(&[], 0);
181 }
182
183 #[test]
184 #[should_panic]
185 fn insert_needs_a_block() {
186 insert(&mut [0u8; 31], 0);
187 }
188
189 #[test]
190 fn union() {
191 let mut a = SplitBlockBloom::with_capacity(100, 8);
192 let mut b = a.clone();
193 a.insert(1);
194 b.insert(2);
195 a.union_with(&b);
196 assert!(a.contains(1) && a.contains(2));
197 }
198}