Skip to main content

velesdb_core/simd_native/dispatch/
hamming.rs

1#[cfg(target_arch = "x86_64")]
2use super::has_avx512vpopcntdq;
3use super::{simd_level, SimdLevel};
4
5/// Hamming distance with runtime SIMD dispatch.
6///
7/// # Panics
8///
9/// Panics if `a.len() != b.len()`.
10#[inline]
11#[must_use]
12pub fn hamming_distance_native(a: &[f32], b: &[f32]) -> f32 {
13    assert_eq!(
14        a.len(),
15        b.len(),
16        "Vector length mismatch: {} vs {}",
17        a.len(),
18        b.len()
19    );
20    hamming_simd(a, b)
21}
22
23/// Jaccard similarity with runtime SIMD dispatch.
24///
25/// # Panics
26///
27/// Panics if `a.len() != b.len()`.
28#[inline]
29#[must_use]
30pub fn jaccard_similarity_native(a: &[f32], b: &[f32]) -> f32 {
31    assert_eq!(
32        a.len(),
33        b.len(),
34        "Vector length mismatch: {} vs {}",
35        a.len(),
36        b.len()
37    );
38    jaccard_simd(a, b)
39}
40
41/// F-08: Use cached `simd_level()` (OnceLock) instead of per-call `is_x86_feature_detected!`
42/// for consistency with dot/cosine/euclidean dispatch paths.
43#[inline]
44fn hamming_simd(a: &[f32], b: &[f32]) -> f32 {
45    match simd_level() {
46        #[cfg(target_arch = "x86_64")]
47        SimdLevel::Avx512 if a.len() >= 512 => {
48            // SAFETY: AVX-512 4-acc hamming kernel requires CPU feature + minimum dim.
49            // - Condition 1: `simd_level()` selected `Avx512` after runtime detection.
50            // SAFETY: 4-accumulator kernel for large vectors.
51            unsafe { crate::simd_native::hamming_avx512_4acc(a, b) }
52        }
53        #[cfg(target_arch = "x86_64")]
54        SimdLevel::Avx512 if a.len() >= 16 => {
55            // SAFETY: AVX-512 hamming kernel requires CPU feature + minimum dim.
56            // - Condition 1: `simd_level()` selected `Avx512` after runtime detection.
57            // SAFETY: call specialized kernel for higher throughput.
58            unsafe { crate::simd_native::hamming_avx512(a, b) }
59        }
60        #[cfg(target_arch = "x86_64")]
61        SimdLevel::Avx512 | SimdLevel::Avx2 if a.len() >= 8 => {
62            // SAFETY: AVX2 hamming kernel requires CPU feature + minimum dim.
63            // - Condition 1: `simd_level()` confirmed AVX2+ (Avx512 implies Avx2 support).
64            // SAFETY: fallthrough for Avx512 with short vectors that don't meet 16-element minimum.
65            unsafe { crate::simd_native::hamming_avx2(a, b) }
66        }
67        #[cfg(target_arch = "aarch64")]
68        SimdLevel::Neon if a.len() >= 4 => crate::simd_native::hamming_neon(a, b),
69        _ => crate::simd_native::scalar::hamming_scalar(a, b),
70    }
71}
72
73/// F-08: Use cached `simd_level()` for consistency with other metrics.
74#[inline]
75fn jaccard_simd(a: &[f32], b: &[f32]) -> f32 {
76    match simd_level() {
77        #[cfg(target_arch = "x86_64")]
78        SimdLevel::Avx512 if a.len() >= 1024 => {
79            // SAFETY: AVX-512 8-acc jaccard kernel requires CPU feature + minimum dim.
80            // - Condition 1: `simd_level()` selected `Avx512` after runtime detection.
81            // SAFETY: 8-accumulator kernel for very large vectors (>= 1024 dims).
82            unsafe { crate::simd_native::jaccard_avx512_8acc(a, b) }
83        }
84        #[cfg(target_arch = "x86_64")]
85        SimdLevel::Avx512 if a.len() >= 512 => {
86            // SAFETY: AVX-512 4-acc jaccard kernel requires CPU feature + minimum dim.
87            // - Condition 1: `simd_level()` selected `Avx512` after runtime detection.
88            // SAFETY: 4-accumulator kernel for large vectors.
89            unsafe { crate::simd_native::jaccard_avx512_4acc(a, b) }
90        }
91        #[cfg(target_arch = "x86_64")]
92        SimdLevel::Avx512 if a.len() >= 16 => {
93            // SAFETY: AVX-512 jaccard kernel requires CPU feature + minimum dim.
94            // - Condition 1: `simd_level()` selected `Avx512` after runtime detection.
95            // SAFETY: call specialized kernel for higher throughput.
96            unsafe { crate::simd_native::jaccard_avx512(a, b) }
97        }
98        #[cfg(target_arch = "x86_64")]
99        SimdLevel::Avx512 | SimdLevel::Avx2 if a.len() >= 8 => {
100            // SAFETY: AVX2 jaccard kernel requires CPU feature + minimum dim.
101            // - Condition 1: `simd_level()` confirmed AVX2+ (Avx512 implies Avx2 support).
102            // SAFETY: fallthrough for Avx512 with short vectors that don't meet 16-element minimum.
103            unsafe { crate::simd_native::jaccard_avx2(a, b) }
104        }
105        #[cfg(target_arch = "aarch64")]
106        SimdLevel::Neon if a.len() >= 4 => crate::simd_native::jaccard_neon(a, b),
107        _ => crate::simd_native::scalar::jaccard_scalar(a, b),
108    }
109}
110
111#[allow(unused_variables)]
112pub(super) fn resolve_hamming(level: SimdLevel, dim: usize) -> fn(&[f32], &[f32]) -> f32 {
113    match level {
114        #[cfg(target_arch = "x86_64")]
115        SimdLevel::Avx512 if dim >= 512 => {
116            |a, b| {
117                // SAFETY: Resolver emitted AVX-512 4-acc implementation for this dimension.
118                // - Condition 1: caller chose this function pointer via `resolve_hamming`.
119                // SAFETY: execute AVX-512 4-accumulator hamming for large vectors.
120                unsafe { crate::simd_native::hamming_avx512_4acc(a, b) }
121            }
122        }
123        #[cfg(target_arch = "x86_64")]
124        SimdLevel::Avx512 if dim >= 16 => {
125            |a, b| {
126                // SAFETY: Resolver emitted AVX-512 implementation for this dimension.
127                // - Condition 1: caller chose this function pointer via `resolve_hamming`.
128                // SAFETY: execute AVX-512 specialized hamming implementation.
129                unsafe { crate::simd_native::hamming_avx512(a, b) }
130            }
131        }
132        #[cfg(target_arch = "x86_64")]
133        SimdLevel::Avx512 | SimdLevel::Avx2 if dim >= 8 => |a, b| {
134            // SAFETY: Resolver emitted AVX2 implementation for this dimension.
135            // - Condition 1: caller chose this function pointer via `resolve_hamming`.
136            // SAFETY: execute AVX2 specialized hamming implementation (Avx512 implies Avx2).
137            unsafe { crate::simd_native::hamming_avx2(a, b) }
138        },
139        #[cfg(target_arch = "aarch64")]
140        SimdLevel::Neon if dim >= 4 => |a, b| crate::simd_native::hamming_neon(a, b),
141        _ => crate::simd_native::scalar::hamming_scalar,
142    }
143}
144
145#[allow(unused_variables)]
146pub(super) fn resolve_jaccard(level: SimdLevel, dim: usize) -> fn(&[f32], &[f32]) -> f32 {
147    match level {
148        #[cfg(target_arch = "x86_64")]
149        SimdLevel::Avx512 if dim >= 1024 => {
150            |a, b| {
151                // SAFETY: Resolver emitted AVX-512 8-acc implementation for this dimension.
152                // - Condition 1: caller chose this function pointer via `resolve_jaccard`.
153                // SAFETY: execute AVX-512 8-accumulator jaccard for very large vectors.
154                unsafe { crate::simd_native::jaccard_avx512_8acc(a, b) }
155            }
156        }
157        #[cfg(target_arch = "x86_64")]
158        SimdLevel::Avx512 if dim >= 512 => {
159            |a, b| {
160                // SAFETY: Resolver emitted AVX-512 4-acc implementation for this dimension.
161                // - Condition 1: caller chose this function pointer via `resolve_jaccard`.
162                // SAFETY: execute AVX-512 4-accumulator jaccard for large vectors.
163                unsafe { crate::simd_native::jaccard_avx512_4acc(a, b) }
164            }
165        }
166        #[cfg(target_arch = "x86_64")]
167        SimdLevel::Avx512 if dim >= 16 => {
168            |a, b| {
169                // SAFETY: Resolver emitted AVX-512 implementation for this dimension.
170                // - Condition 1: caller chose this function pointer via `resolve_jaccard`.
171                // SAFETY: execute AVX-512 specialized jaccard implementation.
172                unsafe { crate::simd_native::jaccard_avx512(a, b) }
173            }
174        }
175        #[cfg(target_arch = "x86_64")]
176        SimdLevel::Avx512 | SimdLevel::Avx2 if dim >= 8 => |a, b| {
177            // SAFETY: Resolver emitted AVX2 implementation for this dimension.
178            // - Condition 1: caller chose this function pointer via `resolve_jaccard`.
179            // SAFETY: execute AVX2 specialized jaccard implementation (Avx512 implies Avx2).
180            unsafe { crate::simd_native::jaccard_avx2(a, b) }
181        },
182        #[cfg(target_arch = "aarch64")]
183        SimdLevel::Neon if dim >= 4 => |a, b| crate::simd_native::jaccard_neon(a, b),
184        _ => crate::simd_native::scalar::jaccard_scalar,
185    }
186}
187
188// =============================================================================
189// Binary Hamming distance (packed u64)
190// =============================================================================
191
192/// Binary Hamming distance with runtime SIMD dispatch.
193///
194/// Operates on packed u64 binary vectors where each bit is a dimension.
195/// XOR + popcount gives the number of differing bits.
196///
197/// # Panics
198///
199/// Panics if `a.len() != b.len()`.
200#[inline]
201#[must_use]
202#[allow(clippy::cast_possible_truncation)]
203// SAFETY: return type is u32; max bits = len * 64 which fits in u32 for any
204// practical binary vector dimension (up to ~67M words).
205pub fn hamming_binary_native(a: &[u64], b: &[u64]) -> u32 {
206    assert_eq!(
207        a.len(),
208        b.len(),
209        "Binary vector length mismatch: {} vs {}",
210        a.len(),
211        b.len()
212    );
213
214    match simd_level() {
215        #[cfg(target_arch = "x86_64")]
216        SimdLevel::Avx512 if a.len() >= 8 && has_avx512vpopcntdq() => {
217            // SAFETY: AVX-512 VPOPCNTDQ binary hamming uses native _mm512_popcnt_epi64.
218            // - Condition 1: `simd_level()` selected `Avx512` after runtime detection.
219            // - Condition 2: `has_avx512vpopcntdq()` confirms hardware popcount support.
220            // SAFETY: native 64-bit popcount avoids extract+scalar loop.
221            unsafe { crate::simd_native::hamming_binary_avx512_vpopcntdq(a, b) }
222        }
223        #[cfg(target_arch = "x86_64")]
224        SimdLevel::Avx512 if a.len() >= 8 => {
225            // SAFETY: AVX-512 binary hamming kernel requires CPU feature + minimum dim.
226            // - Condition 1: `simd_level()` selected `Avx512` after runtime detection.
227            // SAFETY: AVX-512 XOR on 8 packed u64 per iteration for higher throughput.
228            unsafe { crate::simd_native::hamming_binary_avx512(a, b) }
229        }
230        #[cfg(target_arch = "x86_64")]
231        SimdLevel::Avx512 | SimdLevel::Avx2 if a.len() >= 4 => {
232            // SAFETY: AVX2 binary hamming kernel requires CPU feature + minimum dim.
233            // - Condition 1: `simd_level()` confirmed AVX2+ (Avx512 implies Avx2 support).
234            // SAFETY: AVX2 XOR on 4 packed u64 per iteration for higher throughput.
235            unsafe { crate::simd_native::hamming_binary_avx2(a, b) }
236        }
237        #[cfg(target_arch = "aarch64")]
238        SimdLevel::Neon if a.len() >= 2 => {
239            // NEON binary hamming uses vcntq_u8 for byte-popcount on 2 u64 per iteration.
240            crate::simd_native::hamming_binary_neon(a, b)
241        }
242        _ => crate::simd_native::scalar::hamming_binary_scalar(a, b),
243    }
244}
245
246// =============================================================================
247// Batch operations with prefetch (Phase 4)
248// =============================================================================
249
250/// Batch Hamming distance with cross-platform multi-level prefetch hints.
251///
252/// Computes Hamming distance between each candidate and the query vector,
253/// using software prefetching for better cache utilization on large batches.
254#[inline]
255#[must_use]
256pub fn batch_hamming_native(candidates: &[&[f32]], query: &[f32]) -> Vec<f32> {
257    super::batch_with_prefetch(candidates, query, hamming_distance_native)
258}
259
260/// Batch Jaccard similarity with cross-platform multi-level prefetch hints.
261///
262/// Computes Jaccard similarity between each candidate and the query vector,
263/// using software prefetching for better cache utilization on large batches.
264#[inline]
265#[must_use]
266pub fn batch_jaccard_native(candidates: &[&[f32]], query: &[f32]) -> Vec<f32> {
267    super::batch_with_prefetch(candidates, query, jaccard_similarity_native)
268}