writerslogic_warm/core/
intersection.rs1#[inline]
15pub fn sparse_intersection_count(a: &[u32], b: &[u32]) -> usize {
16 if a.is_empty() || b.is_empty() {
17 return 0;
18 }
19
20 let (small, large) = if a.len() <= b.len() { (a, b) } else { (b, a) };
22 if small.len() * 8 < large.len() {
25 return galloping_intersection_count(small, large);
26 }
27
28 #[cfg(target_arch = "x86_64")]
29 if let Some(count) = crate::core::simd_math::simd_intersection_count(small, large) {
30 return count;
31 }
32
33 merge_intersection_count(small, large)
34}
35
36#[inline]
38fn merge_intersection_count(a: &[u32], b: &[u32]) -> usize {
39 let mut count = 0;
40 let mut i = 0;
41 let mut j = 0;
42 while i < a.len() && j < b.len() {
43 match a[i].cmp(&b[j]) {
44 std::cmp::Ordering::Less => i += 1,
45 std::cmp::Ordering::Greater => j += 1,
46 std::cmp::Ordering::Equal => {
47 count += 1;
48 i += 1;
49 j += 1;
50 }
51 }
52 }
53 count
54}
55
56#[inline]
62fn galloping_intersection_count(small: &[u32], large: &[u32]) -> usize {
63 let mut count = 0;
64 let mut lo = 0; for &val in small {
66 let mut step = 1;
69 while lo + step < large.len() && large[lo + step] < val {
70 step *= 2;
71 }
72 let hi = (lo + step).min(large.len());
74 lo = binary_search_left(&large[lo..hi], val) + lo;
76 if lo < large.len() && large[lo] == val {
77 count += 1;
78 lo += 1; }
80 }
81 count
82}
83
84#[inline]
86fn binary_search_left(slice: &[u32], target: u32) -> usize {
87 let mut lo = 0;
88 let mut hi = slice.len();
89 while lo < hi {
90 let mid = lo + (hi - lo) / 2;
91 if slice[mid] < target {
92 lo = mid + 1;
93 } else {
94 hi = mid;
95 }
96 }
97 lo
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 fn naive(a: &[u32], b: &[u32]) -> usize {
105 let set: std::collections::HashSet<u32> = b.iter().copied().collect();
106 a.iter().filter(|x| set.contains(x)).count()
107 }
108
109 struct Lcg(u64);
110 impl Lcg {
111 fn next(&mut self) -> u64 {
112 self.0 = self
113 .0
114 .wrapping_mul(6364136223846793005)
115 .wrapping_add(1442695040888963407);
116 self.0 >> 33
117 }
118 }
119
120 fn sample(rng: &mut Lcg, n: usize, universe: u64) -> Vec<u32> {
122 let mut set = std::collections::BTreeSet::new();
123 while set.len() < n {
124 set.insert((rng.next() % universe) as u32);
125 }
126 set.into_iter().collect()
127 }
128
129 fn check_all(a: &[u32], b: &[u32]) {
130 let want = naive(a, b);
131 assert_eq!(sparse_intersection_count(a, b), want, "dispatch a,b");
132 assert_eq!(sparse_intersection_count(b, a), want, "dispatch b,a");
133 assert_eq!(merge_intersection_count(a, b), want, "merge");
134 let (small, large) = if a.len() <= b.len() { (a, b) } else { (b, a) };
135 assert_eq!(galloping_intersection_count(small, large), want, "gallop");
136 #[cfg(target_arch = "x86_64")]
137 if let Some(c) = crate::core::simd_math::simd_intersection_count(a, b) {
138 assert_eq!(c, want, "avx2");
139 }
140 }
141
142 #[test]
143 fn all_kernels_match_naive_count() {
144 for seed in 0..300u64 {
145 let mut rng = Lcg(seed);
146 let na = (rng.next() % 2001) as usize;
147 let nb = (rng.next() % 2001) as usize;
148 let universe = (na.max(nb) as u64 + 1) * (1 + rng.next() % 8);
150 check_all(
151 &sample(&mut rng, na, universe),
152 &sample(&mut rng, nb, universe),
153 );
154 }
155 }
156
157 #[test]
158 fn kernels_match_on_edge_shapes_and_tails() {
159 let mut rng = Lcg(7);
160 for na in 0..=17usize {
161 for nb in (0..=17usize).chain([64, 65, 600]) {
162 for universe in [40u64, 2000, 1 << 20] {
163 let universe = universe.max(na.max(nb) as u64);
164 check_all(
165 &sample(&mut rng, na, universe),
166 &sample(&mut rng, nb, universe),
167 );
168 }
169 }
170 }
171 let ident = sample(&mut rng, 777, 5000);
172 check_all(&ident, &ident);
173 let evens: Vec<u32> = (0..1000).map(|x| x * 2).collect();
174 let odds: Vec<u32> = (0..1000).map(|x| x * 2 + 1).collect();
175 check_all(&evens, &odds);
176 check_all(&[], &odds);
177 check_all(&[], &[]);
178 }
179}