1pub const EDGE_SIZE: usize = 128;
39
40pub const PREFETCH_DISTANCE: usize = 16;
43
44#[cfg(unix)]
58pub fn advise_sequential(data: &[u8], start_offset: usize, length: usize) {
59 use std::cmp::min;
60
61 let start = min(start_offset, data.len());
63 let end = min(start + length, data.len());
64
65 if end <= start {
66 return;
67 }
68
69 let page_size = page_size();
71 let aligned_start = (start / page_size) * page_size;
72 let aligned_length = (end - aligned_start).div_ceil(page_size) * page_size;
73
74 unsafe {
75 let ptr = data.as_ptr().add(aligned_start) as *mut libc::c_void;
76
77 let advice = libc::MADV_SEQUENTIAL | libc::MADV_WILLNEED;
80
81 let result = libc::madvise(ptr, aligned_length, advice);
82 if result != 0 {
83 #[cfg(debug_assertions)]
85 eprintln!(
86 "madvise failed: {} (continuing without prefetch)",
87 std::io::Error::last_os_error()
88 );
89 }
90 }
91}
92
93#[cfg(not(unix))]
94pub fn advise_sequential(_data: &[u8], _start_offset: usize, _length: usize) {
95 }
97
98#[cfg(unix)]
102pub fn advise_random(data: &[u8]) {
103 unsafe {
104 let ptr = data.as_ptr() as *mut libc::c_void;
105 let result = libc::madvise(ptr, data.len(), libc::MADV_RANDOM);
106 if result != 0 {
107 #[cfg(debug_assertions)]
108 eprintln!("madvise RANDOM failed: {}", std::io::Error::last_os_error());
109 }
110 }
111}
112
113#[cfg(not(unix))]
114pub fn advise_random(_data: &[u8]) {
115 }
117
118#[cfg(unix)]
122pub fn advise_dontneed(data: &[u8], start_offset: usize, length: usize) {
123 use std::cmp::min;
124
125 let start = min(start_offset, data.len());
126 let end = min(start + length, data.len());
127
128 if end <= start {
129 return;
130 }
131
132 let page_size = page_size();
133 let aligned_start = (start / page_size) * page_size;
134 let aligned_length = (end - aligned_start).div_ceil(page_size) * page_size;
135
136 unsafe {
137 let ptr = data.as_ptr().add(aligned_start) as *mut libc::c_void;
138 libc::madvise(ptr, aligned_length, libc::MADV_DONTNEED);
140 }
141}
142
143#[cfg(not(unix))]
144pub fn advise_dontneed(_data: &[u8], _start_offset: usize, _length: usize) {
145 }
147
148#[cfg(all(target_arch = "x86_64", target_feature = "sse"))]
161pub fn prefetch_ahead(data: &[u8], current_offset: usize, prefetch_distance: usize) {
162 use std::arch::x86_64::{_MM_HINT_T0, _mm_prefetch};
163
164 let target_offset = current_offset + prefetch_distance;
165 if target_offset < data.len() {
166 unsafe {
167 _mm_prefetch(
168 data.as_ptr().add(target_offset) as *const i8,
169 _MM_HINT_T0, );
171 }
172 }
173}
174
175#[cfg(target_arch = "aarch64")]
176pub fn prefetch_ahead(data: &[u8], current_offset: usize, prefetch_distance: usize) {
177 let _ = (data, current_offset, prefetch_distance);
181}
182
183#[cfg(not(any(
184 all(target_arch = "x86_64", target_feature = "sse"),
185 target_arch = "aarch64"
186)))]
187pub fn prefetch_ahead(_data: &[u8], _current_offset: usize, _prefetch_distance: usize) {
188 }
190
191#[cfg(unix)]
193fn page_size() -> usize {
194 unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
195}
196
197#[cfg(not(unix))]
198fn page_size() -> usize {
199 4096 }
201
202pub struct PrefetchingIterator<'a, I> {
207 inner: I,
208 data: &'a [u8],
209 current_offset: usize,
210 prefetch_distance_bytes: usize,
211}
212
213impl<'a, I> PrefetchingIterator<'a, I> {
214 pub fn new(inner: I, data: &'a [u8], prefetch_distance: usize, item_size: usize) -> Self {
222 Self {
223 inner,
224 data,
225 current_offset: 0,
226 prefetch_distance_bytes: prefetch_distance * item_size,
227 }
228 }
229
230 pub fn set_offset(&mut self, offset: usize) {
232 self.current_offset = offset;
233 }
234}
235
236impl<'a, I, T> Iterator for PrefetchingIterator<'a, I>
237where
238 I: Iterator<Item = T>,
239{
240 type Item = T;
241
242 fn next(&mut self) -> Option<Self::Item> {
243 prefetch_ahead(self.data, self.current_offset, self.prefetch_distance_bytes);
245
246 match self.inner.next() {
247 Some(item) => {
248 self.current_offset += EDGE_SIZE; Some(item)
250 }
251 None => None,
252 }
253 }
254}
255
256#[derive(Debug, Default, Clone)]
258pub struct PrefetchStats {
259 pub madvise_calls: u64,
261 pub prefetch_calls: u64,
263 pub bytes_advised: u64,
265}
266
267impl PrefetchStats {
268 pub fn new() -> Self {
270 Self::default()
271 }
272
273 pub fn record_madvise(&mut self, bytes: usize) {
275 self.madvise_calls += 1;
276 self.bytes_advised += bytes as u64;
277 }
278
279 pub fn record_prefetch(&mut self) {
281 self.prefetch_calls += 1;
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[test]
290 fn test_page_size() {
291 let ps = page_size();
292 assert!(ps >= 4096, "Page size too small: {}", ps);
293 assert!(ps.is_power_of_two(), "Page size not power of 2: {}", ps);
294 }
295
296 #[test]
297 fn test_advise_sequential_bounds() {
298 let data = vec![0u8; 4096 * 10]; advise_sequential(&data, 0, data.len());
302
303 advise_sequential(&data, 4096, 8192);
305
306 advise_sequential(&data, data.len() + 1000, 1000);
308
309 advise_sequential(&data, 0, 0);
311 }
312
313 #[test]
314 fn test_prefetch_ahead_bounds() {
315 let data = vec![0u8; 1024];
316
317 prefetch_ahead(&data, 0, 512);
319
320 prefetch_ahead(&data, 900, 200);
322
323 prefetch_ahead(&data, 1000, 500);
325 }
326
327 #[test]
328 fn test_prefetching_iterator() {
329 let data = vec![0u8; 128 * 100]; let items: Vec<i32> = (0..100).collect();
331
332 let iter = PrefetchingIterator::new(items.into_iter(), &data, PREFETCH_DISTANCE, EDGE_SIZE);
333
334 let collected: Vec<i32> = iter.collect();
335 assert_eq!(collected.len(), 100);
336 assert_eq!(collected[0], 0);
337 assert_eq!(collected[99], 99);
338 }
339
340 #[test]
341 fn test_prefetch_stats() {
342 let mut stats = PrefetchStats::new();
343
344 stats.record_madvise(4096);
345 stats.record_madvise(8192);
346 stats.record_prefetch();
347 stats.record_prefetch();
348 stats.record_prefetch();
349
350 assert_eq!(stats.madvise_calls, 2);
351 assert_eq!(stats.prefetch_calls, 3);
352 assert_eq!(stats.bytes_advised, 4096 + 8192);
353 }
354}