pub const EDGE_SIZE: usize = 128;
pub const PREFETCH_DISTANCE: usize = 16;
#[cfg(unix)]
pub fn advise_sequential(data: &[u8], start_offset: usize, length: usize) {
use std::cmp::min;
let start = min(start_offset, data.len());
let end = min(start + length, data.len());
if end <= start {
return;
}
let page_size = page_size();
let aligned_start = (start / page_size) * page_size;
let aligned_length = (end - aligned_start).div_ceil(page_size) * page_size;
unsafe {
let ptr = data.as_ptr().add(aligned_start) as *mut libc::c_void;
let advice = libc::MADV_SEQUENTIAL | libc::MADV_WILLNEED;
let result = libc::madvise(ptr, aligned_length, advice);
if result != 0 {
#[cfg(debug_assertions)]
eprintln!(
"madvise failed: {} (continuing without prefetch)",
std::io::Error::last_os_error()
);
}
}
}
#[cfg(not(unix))]
pub fn advise_sequential(_data: &[u8], _start_offset: usize, _length: usize) {
}
#[cfg(unix)]
pub fn advise_random(data: &[u8]) {
unsafe {
let ptr = data.as_ptr() as *mut libc::c_void;
let result = libc::madvise(ptr, data.len(), libc::MADV_RANDOM);
if result != 0 {
#[cfg(debug_assertions)]
eprintln!("madvise RANDOM failed: {}", std::io::Error::last_os_error());
}
}
}
#[cfg(not(unix))]
pub fn advise_random(_data: &[u8]) {
}
#[cfg(unix)]
pub fn advise_dontneed(data: &[u8], start_offset: usize, length: usize) {
use std::cmp::min;
let start = min(start_offset, data.len());
let end = min(start + length, data.len());
if end <= start {
return;
}
let page_size = page_size();
let aligned_start = (start / page_size) * page_size;
let aligned_length = (end - aligned_start).div_ceil(page_size) * page_size;
unsafe {
let ptr = data.as_ptr().add(aligned_start) as *mut libc::c_void;
libc::madvise(ptr, aligned_length, libc::MADV_DONTNEED);
}
}
#[cfg(not(unix))]
pub fn advise_dontneed(_data: &[u8], _start_offset: usize, _length: usize) {
}
#[cfg(all(target_arch = "x86_64", target_feature = "sse"))]
pub fn prefetch_ahead(data: &[u8], current_offset: usize, prefetch_distance: usize) {
use std::arch::x86_64::{_MM_HINT_T0, _mm_prefetch};
let target_offset = current_offset + prefetch_distance;
if target_offset < data.len() {
unsafe {
_mm_prefetch(
data.as_ptr().add(target_offset) as *const i8,
_MM_HINT_T0, );
}
}
}
#[cfg(target_arch = "aarch64")]
pub fn prefetch_ahead(data: &[u8], current_offset: usize, prefetch_distance: usize) {
let _ = (data, current_offset, prefetch_distance);
}
#[cfg(not(any(
all(target_arch = "x86_64", target_feature = "sse"),
target_arch = "aarch64"
)))]
pub fn prefetch_ahead(_data: &[u8], _current_offset: usize, _prefetch_distance: usize) {
}
#[cfg(unix)]
fn page_size() -> usize {
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
}
#[cfg(not(unix))]
fn page_size() -> usize {
4096 }
pub struct PrefetchingIterator<'a, I> {
inner: I,
data: &'a [u8],
current_offset: usize,
prefetch_distance_bytes: usize,
}
impl<'a, I> PrefetchingIterator<'a, I> {
pub fn new(inner: I, data: &'a [u8], prefetch_distance: usize, item_size: usize) -> Self {
Self {
inner,
data,
current_offset: 0,
prefetch_distance_bytes: prefetch_distance * item_size,
}
}
pub fn set_offset(&mut self, offset: usize) {
self.current_offset = offset;
}
}
impl<'a, I, T> Iterator for PrefetchingIterator<'a, I>
where
I: Iterator<Item = T>,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
prefetch_ahead(self.data, self.current_offset, self.prefetch_distance_bytes);
match self.inner.next() {
Some(item) => {
self.current_offset += EDGE_SIZE; Some(item)
}
None => None,
}
}
}
#[derive(Debug, Default, Clone)]
pub struct PrefetchStats {
pub madvise_calls: u64,
pub prefetch_calls: u64,
pub bytes_advised: u64,
}
impl PrefetchStats {
pub fn new() -> Self {
Self::default()
}
pub fn record_madvise(&mut self, bytes: usize) {
self.madvise_calls += 1;
self.bytes_advised += bytes as u64;
}
pub fn record_prefetch(&mut self) {
self.prefetch_calls += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_page_size() {
let ps = page_size();
assert!(ps >= 4096, "Page size too small: {}", ps);
assert!(ps.is_power_of_two(), "Page size not power of 2: {}", ps);
}
#[test]
fn test_advise_sequential_bounds() {
let data = vec![0u8; 4096 * 10];
advise_sequential(&data, 0, data.len());
advise_sequential(&data, 4096, 8192);
advise_sequential(&data, data.len() + 1000, 1000);
advise_sequential(&data, 0, 0);
}
#[test]
fn test_prefetch_ahead_bounds() {
let data = vec![0u8; 1024];
prefetch_ahead(&data, 0, 512);
prefetch_ahead(&data, 900, 200);
prefetch_ahead(&data, 1000, 500);
}
#[test]
fn test_prefetching_iterator() {
let data = vec![0u8; 128 * 100]; let items: Vec<i32> = (0..100).collect();
let iter = PrefetchingIterator::new(items.into_iter(), &data, PREFETCH_DISTANCE, EDGE_SIZE);
let collected: Vec<i32> = iter.collect();
assert_eq!(collected.len(), 100);
assert_eq!(collected[0], 0);
assert_eq!(collected[99], 99);
}
#[test]
fn test_prefetch_stats() {
let mut stats = PrefetchStats::new();
stats.record_madvise(4096);
stats.record_madvise(8192);
stats.record_prefetch();
stats.record_prefetch();
stats.record_prefetch();
assert_eq!(stats.madvise_calls, 2);
assert_eq!(stats.prefetch_calls, 3);
assert_eq!(stats.bytes_advised, 4096 + 8192);
}
}