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}