pub const CACHE_LINE_SIZE: usize = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PrefetchHint {
#[default]
Temporal,
NonTemporal,
Exclusive,
}
#[inline(always)]
#[cfg(target_arch = "x86_64")]
pub unsafe fn prefetch(ptr: *const u8, hint: PrefetchHint) {
unsafe {
#[cfg(target_feature = "sse")]
{
use std::arch::x86_64::*;
match hint {
PrefetchHint::Temporal => _mm_prefetch(ptr as *const i8, _MM_HINT_T0),
PrefetchHint::NonTemporal => _mm_prefetch(ptr as *const i8, _MM_HINT_NTA),
PrefetchHint::Exclusive => _mm_prefetch(ptr as *const i8, _MM_HINT_T0),
}
}
}
}
#[inline(always)]
#[cfg(not(target_arch = "x86_64"))]
pub unsafe fn prefetch(_ptr: *const u8, _hint: PrefetchHint) {
}
#[inline]
pub unsafe fn prefetch_lines(ptr: *const u8, lines: usize, hint: PrefetchHint) {
unsafe {
for i in 0..lines {
prefetch(ptr.add(i * CACHE_LINE_SIZE), hint);
}
}
}
#[inline]
pub unsafe fn prefetch_range(ptr: *const u8, size: usize, hint: PrefetchHint) {
unsafe {
let lines = size.div_ceil(CACHE_LINE_SIZE);
prefetch_lines(ptr, lines, hint);
}
}
pub struct PrefetchIter<I, T> {
inner: I,
lookahead: usize,
_marker: std::marker::PhantomData<T>,
}
impl<I, T> PrefetchIter<I, T>
where
I: Iterator<Item = T>,
{
pub fn new(iter: I, lookahead: usize) -> Self {
Self {
inner: iter,
lookahead,
_marker: std::marker::PhantomData,
}
}
#[inline(always)]
pub fn lookahead(&self) -> usize {
self.lookahead
}
}
impl<I, T> Iterator for PrefetchIter<I, T>
where
I: Iterator<Item = T>,
{
type Item = T;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
pub struct PrefetchProcessor<'a, T> {
data: &'a [T],
position: usize,
prefetch_distance: usize,
}
impl<'a, T> PrefetchProcessor<'a, T> {
pub fn new(data: &'a [T], prefetch_distance: usize) -> Self {
Self {
data,
position: 0,
prefetch_distance,
}
}
#[inline(always)]
pub fn remaining(&self) -> usize {
self.data.len() - self.position
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.position >= self.data.len()
}
#[inline]
pub fn next(&mut self) -> Option<&T> {
if self.position >= self.data.len() {
return None;
}
let prefetch_idx = self.position + self.prefetch_distance;
if prefetch_idx < self.data.len() {
unsafe {
let ptr = &self.data[prefetch_idx] as *const T as *const u8;
prefetch(ptr, PrefetchHint::Temporal);
}
}
let item = &self.data[self.position];
self.position += 1;
Some(item)
}
#[inline]
pub fn for_each<F>(&mut self, mut f: F)
where
F: FnMut(&T),
{
while let Some(item) = self.next() {
f(item);
}
}
pub fn map<U, F>(&mut self, mut f: F) -> Vec<U>
where
F: FnMut(&T) -> U,
{
let mut results = Vec::with_capacity(self.remaining());
while let Some(item) = self.next() {
results.push(f(item));
}
results
}
pub fn fold<U, F>(&mut self, init: U, mut f: F) -> U
where
F: FnMut(U, &T) -> U,
{
let mut acc = init;
while let Some(item) = self.next() {
acc = f(acc, item);
}
acc
}
}
pub mod records {
use super::*;
#[inline]
pub fn prefetch_records(
data: &[u8],
current_record: usize,
record_size: usize,
records_ahead: usize,
) {
let target_record = current_record + records_ahead;
let offset = target_record * record_size;
if offset < data.len() {
unsafe {
let lines = record_size.div_ceil(CACHE_LINE_SIZE);
let ptr = data.as_ptr().add(offset);
prefetch_lines(ptr, lines, PrefetchHint::Temporal);
}
}
}
pub fn process_with_prefetch<'a, F, R>(
data: &'a [u8],
record_size: usize,
record_count: usize,
records_ahead: usize,
mut processor: F,
) -> Vec<R>
where
F: FnMut(&'a [u8], usize) -> R,
{
let mut results = Vec::with_capacity(record_count);
for i in 0..record_count {
prefetch_records(data, i, record_size, records_ahead);
let offset = i * record_size;
let record_data = &data[offset..offset + record_size];
results.push(processor(record_data, i));
}
results
}
pub fn sum_field_prefetch(
data: &[u8],
field_offset: usize,
record_size: usize,
record_count: usize,
) -> u64 {
let mut sum = 0u64;
const PREFETCH_DISTANCE: usize = 8;
for i in 0..record_count {
if i + PREFETCH_DISTANCE < record_count {
let pf_offset = (i + PREFETCH_DISTANCE) * record_size + field_offset;
if pf_offset + 8 <= data.len() {
unsafe {
prefetch(data.as_ptr().add(pf_offset), PrefetchHint::Temporal);
}
}
}
let offset = i * record_size + field_offset;
let value = u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]);
sum += value;
}
sum
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prefetch_processor() {
let data = vec![1u64, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let mut processor = PrefetchProcessor::new(&data, 4);
let sum = processor.fold(0u64, |acc, &x| acc + x);
assert_eq!(sum, 55);
}
#[test]
fn test_prefetch_processor_map() {
let data = vec![1u32, 2, 3, 4, 5];
let mut processor = PrefetchProcessor::new(&data, 2);
let doubled: Vec<u32> = processor.map(|&x| x * 2);
assert_eq!(doubled, vec![2, 4, 6, 8, 10]);
}
#[test]
fn test_prefetch_iter_lookahead() {
let data = vec![1, 2, 3, 4, 5];
let iter = PrefetchIter::new(data.into_iter(), 4);
assert_eq!(iter.lookahead(), 4);
}
#[test]
fn test_record_prefetch_sum() {
let record_size = 24; let record_count = 10;
let mut data = vec![0u8; record_size * record_count];
for i in 0..record_count {
let id = (i + 1) as u64 * 100;
let offset = i * record_size;
data[offset..offset + 8].copy_from_slice(&id.to_le_bytes());
}
let sum = records::sum_field_prefetch(&data, 0, record_size, record_count);
assert_eq!(sum, 5500); }
#[test]
fn test_process_with_prefetch() {
let record_size = 16;
let record_count = 5;
let mut data = vec![0u8; record_size * record_count];
for i in 0..record_count {
let id = i as u64;
let offset = i * record_size;
data[offset..offset + 8].copy_from_slice(&id.to_le_bytes());
}
let results = records::process_with_prefetch(
&data,
record_size,
record_count,
4,
|record_data, _idx| u64::from_le_bytes(record_data[0..8].try_into().unwrap()),
);
assert_eq!(results, vec![0, 1, 2, 3, 4]);
}
}