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