Skip to main content

velesdb_core/simd_native/
prefetch.rs

1//! CPU cache prefetch utilities for SIMD operations.
2//!
3//! Provides software prefetching hints to warm up CPU caches before
4//! SIMD data access, reducing memory latency in batch operations.
5
6/// L2 cache line size in bytes (standard for modern x86_64 CPUs).
7pub const L2_CACHE_LINE_BYTES: usize = 64;
8
9/// Calculates optimal prefetch distance based on vector dimension.
10///
11/// # Algorithm
12///
13/// Prefetch distance is computed to stay within L2 cache constraints:
14/// - `distance = (vector_bytes / L2_CACHE_LINE).clamp(4, 16)`
15/// - Minimum 4: Ensure enough lookahead for out-of-order execution
16/// - Maximum 16: Prevent cache pollution from over-prefetching
17#[inline]
18#[must_use]
19pub const fn calculate_prefetch_distance(dimension: usize) -> usize {
20    let vector_bytes = dimension * std::mem::size_of::<f32>();
21    let raw_distance = vector_bytes / L2_CACHE_LINE_BYTES;
22    // Manual clamp for const fn
23    if raw_distance < 4 {
24        4
25    } else if raw_distance > 16 {
26        16
27    } else {
28        raw_distance
29    }
30}
31
32/// Prefetches a vector into L1 cache (T0 hint) for upcoming SIMD operations.
33///
34/// # Platform Support
35///
36/// - **x86_64**: Uses `_mm_prefetch` with `_MM_HINT_T0`
37/// - **aarch64**: Uses inline ASM workaround (rust-lang/rust#117217)
38/// - **Other**: No-op (graceful degradation)
39///
40/// # Safety
41///
42/// This function is safe because prefetch instructions are hints and cannot
43/// cause memory faults even with invalid addresses.
44#[inline]
45pub fn prefetch_vector(vector: &[f32]) {
46    if vector.is_empty() {
47        return;
48    }
49
50    #[cfg(target_arch = "x86_64")]
51    {
52        // SAFETY: _mm_prefetch is a hint instruction that cannot cause memory faults.
53        // - Condition 1: The pointer is derived from a valid slice reference (non-empty check above)
54        // - Condition 2: Prefetch instructions are hints and never fault, even with invalid addresses
55        // - Condition 3: x86_64 architecture guarantees _mm_prefetch availability
56        // SAFETY: Software prefetching for cache optimization before SIMD data access.
57        unsafe {
58            use std::arch::x86_64::{_mm_prefetch, _MM_HINT_T0};
59            _mm_prefetch(vector.as_ptr().cast::<i8>(), _MM_HINT_T0);
60        }
61    }
62
63    #[cfg(target_arch = "aarch64")]
64    {
65        crate::simd_neon_prefetch::prefetch_vector_neon(vector);
66    }
67
68    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
69    {
70        let _ = vector;
71    }
72}
73
74/// Prefetches a `u16` slice into L1 cache (cross-platform).
75///
76/// Used by ADC batch operations to prefetch the next PQ code vector.
77#[inline]
78pub fn prefetch_vector_from_u16(data: &[u16]) {
79    if data.is_empty() {
80        return;
81    }
82
83    #[cfg(target_arch = "x86_64")]
84    {
85        // SAFETY: _mm_prefetch is a hint instruction that cannot cause memory faults.
86        // - Condition 1: The pointer is derived from a valid slice reference.
87        // - Condition 2: Prefetch hints never fault, even with invalid addresses.
88        // SAFETY: Software prefetching for ADC code vectors before gather operations.
89        unsafe {
90            use std::arch::x86_64::{_mm_prefetch, _MM_HINT_T0};
91            _mm_prefetch(data.as_ptr().cast::<i8>(), _MM_HINT_T0);
92        }
93    }
94
95    #[cfg(target_arch = "aarch64")]
96    {
97        crate::simd_neon_prefetch::prefetch_read_l1(data.as_ptr().cast::<u8>());
98    }
99
100    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
101    {
102        let _ = data;
103    }
104}
105
106/// Prefetches a `u64` slice into L1 cache (cross-platform).
107///
108/// Used by `RaBitQ` vector store to prefetch binary codes before
109/// XOR + popcount distance computation in the graph traversal loop.
110#[inline]
111pub fn prefetch_vector_u64(data: &[u64]) {
112    if data.is_empty() {
113        return;
114    }
115
116    #[cfg(target_arch = "x86_64")]
117    {
118        // SAFETY: _mm_prefetch is a hint instruction that cannot cause memory faults.
119        // - Condition 1: The pointer is derived from a valid slice reference.
120        // - Condition 2: Prefetch hints never fault, even with invalid addresses.
121        // SAFETY: Software prefetching for RaBitQ binary codes before XOR+popcount.
122        unsafe {
123            use std::arch::x86_64::{_mm_prefetch, _MM_HINT_T0};
124            _mm_prefetch(data.as_ptr().cast::<i8>(), _MM_HINT_T0);
125        }
126    }
127
128    #[cfg(target_arch = "aarch64")]
129    {
130        crate::simd_neon_prefetch::prefetch_read_l1(data.as_ptr().cast::<u8>());
131    }
132
133    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
134    {
135        let _ = data;
136    }
137}
138
139/// Prefetches a vector into multiple cache levels for larger vectors.
140///
141/// Coverage strategy per architecture:
142/// - **`x86_64`**: 4 lines at 64B stride (offsets 0, 64, 128, 256)
143/// - **`aarch64`**: 4 lines at 128B stride (Apple Silicon cache line size)
144/// - **Other**: no-op
145#[inline]
146pub fn prefetch_vector_multi_cache_line(vector: &[f32]) {
147    if vector.is_empty() {
148        return;
149    }
150
151    #[cfg(target_arch = "x86_64")]
152    {
153        prefetch_multi_x86(vector);
154    }
155
156    #[cfg(target_arch = "aarch64")]
157    {
158        prefetch_multi_arm64(vector);
159    }
160
161    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
162    {
163        let _ = vector;
164    }
165}
166
167/// x86_64 multi-cache-line prefetch at 64B stride.
168#[cfg(target_arch = "x86_64")]
169#[inline]
170fn prefetch_multi_x86(vector: &[f32]) {
171    use std::arch::x86_64::{_mm_prefetch, _MM_HINT_T0, _MM_HINT_T1, _MM_HINT_T2};
172
173    let vector_bytes = std::mem::size_of_val(vector);
174
175    // SAFETY: _mm_prefetch is a non-faulting hint instruction.
176    // - Condition 1: Pointers derived from valid slice reference.
177    // - Condition 2: Offsets checked against vector_bytes.
178    // SAFETY: Multi-level cache warming for large vectors before SIMD processing.
179    unsafe {
180        _mm_prefetch(vector.as_ptr().cast::<i8>(), _MM_HINT_T0);
181
182        if vector_bytes > L2_CACHE_LINE_BYTES {
183            _mm_prefetch(
184                vector.as_ptr().cast::<i8>().add(L2_CACHE_LINE_BYTES),
185                _MM_HINT_T1,
186            );
187        }
188        if vector_bytes > L2_CACHE_LINE_BYTES * 2 {
189            _mm_prefetch(
190                vector.as_ptr().cast::<i8>().add(L2_CACHE_LINE_BYTES * 2),
191                _MM_HINT_T2,
192            );
193        }
194        if vector_bytes > L2_CACHE_LINE_BYTES * 4 {
195            _mm_prefetch(
196                vector.as_ptr().cast::<i8>().add(L2_CACHE_LINE_BYTES * 4),
197                _MM_HINT_T2,
198            );
199        }
200    }
201}
202
203/// ARM64 multi-cache-line prefetch at 128B stride (Apple Silicon cache line size).
204///
205/// Apple M1-M4 use 128-byte cache lines. Graviton uses 64B but a 128B stride
206/// still provides useful lookahead. Prefetches 4 lines for vectors > 512B.
207#[cfg(target_arch = "aarch64")]
208#[inline]
209fn prefetch_multi_arm64(vector: &[f32]) {
210    const ARM_CL: usize = 128;
211    let base = vector.as_ptr().cast::<u8>();
212    let vector_bytes = std::mem::size_of_val(vector);
213
214    // Line 0 → L1
215    crate::simd_neon_prefetch::prefetch_read_l1(base);
216
217    // Line 1 → L1 (offset 128B)
218    if vector_bytes > ARM_CL {
219        // SAFETY: Prefetch is a non-faulting hint; offset < vector_bytes.
220        // - Condition 1: `vector_bytes > ARM_CL` ensures offset is within allocation.
221        // SAFETY: Prefetch next cache line into L1 for spatial locality.
222        let ptr = unsafe { base.add(ARM_CL) };
223        crate::simd_neon_prefetch::prefetch_read_l1(ptr);
224    }
225    // Line 2 → L2 (offset 256B)
226    if vector_bytes > ARM_CL * 2 {
227        // SAFETY: Prefetch is a non-faulting hint; offset < vector_bytes.
228        // - Condition 1: `vector_bytes > ARM_CL * 2` ensures offset is within allocation.
229        // SAFETY: Prefetch farther cache line into L2 for streaming access.
230        let ptr = unsafe { base.add(ARM_CL * 2) };
231        crate::simd_neon_prefetch::prefetch_read_l2(ptr);
232    }
233    // Line 3 → L3 (offset 512B)
234    if vector_bytes > ARM_CL * 4 {
235        // SAFETY: Prefetch is a non-faulting hint; offset < vector_bytes.
236        // - Condition 1: `vector_bytes > ARM_CL * 4` ensures offset is within allocation.
237        // SAFETY: Prefetch distant cache line into L3 for large vector lookahead.
238        let ptr = unsafe { base.add(ARM_CL * 4) };
239        crate::simd_neon_prefetch::prefetch_read_l3(ptr);
240    }
241}