use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
use std::hint::black_box;
use strided_view::auxiliary::index_order;
use strided_view::{
col_major_strides, row_major_strides, StrideVec, StridedArray, StridedView, StridedViewMut,
INLINE_RANK,
};
struct CountingAllocator;
thread_local! {
static THREAD_ALLOCATIONS: Cell<usize> = const { Cell::new(0) };
}
fn record_allocation() {
let _ = THREAD_ALLOCATIONS.try_with(|count| count.set(count.get() + 1));
}
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
record_allocation();
System.alloc(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
System.dealloc(ptr, layout)
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
record_allocation();
System.realloc(ptr, layout, new_size)
}
}
#[global_allocator]
static GLOBAL: CountingAllocator = CountingAllocator;
fn count_allocations(run: impl FnOnce()) -> usize {
let before = THREAD_ALLOCATIONS.with(Cell::get);
run();
THREAD_ALLOCATIONS.with(Cell::get) - before
}
fn dims_for(rank: usize) -> Vec<usize> {
(0..rank).map(|i| i + 2).collect()
}
fn contiguous_case(rank: usize) -> (Vec<usize>, StrideVec, Vec<f64>) {
let dims = dims_for(rank);
let strides = col_major_strides(&dims);
let total: usize = dims.iter().product();
let data = vec![1.0f64; total.max(1)];
(dims, strides, data)
}
#[test]
fn inline_rank_is_eight() {
assert_eq!(INLINE_RANK, 8);
}
#[test]
fn construction_allocates_nothing_up_to_inline_rank() {
for rank in 1..=INLINE_RANK {
let (dims, strides, data) = contiguous_case(rank);
let allocations = count_allocations(|| {
let view = StridedView::<f64>::new(black_box(&data), &dims, &strides, 0).unwrap();
black_box(view.dims().len());
});
assert_eq!(allocations, 0, "StridedView::new rank {rank} allocated");
let mut data_mut = data.clone();
let allocations = count_allocations(|| {
let view =
StridedViewMut::<f64>::new(black_box(&mut data_mut), &dims, &strides, 0).unwrap();
black_box(view.dims().len());
});
assert_eq!(allocations, 0, "StridedViewMut::new rank {rank} allocated");
}
}
#[test]
fn clone_conj_and_derived_views_allocate_nothing_up_to_inline_rank() {
for rank in 1..=INLINE_RANK {
let (dims, strides, data) = contiguous_case(rank);
let view = StridedView::<f64>::new(&data, &dims, &strides, 0).unwrap();
let perm: Vec<usize> = (0..rank).rev().collect();
let allocations = count_allocations(|| {
black_box(view.clone());
});
assert_eq!(allocations, 0, "clone rank {rank} allocated");
let allocations = count_allocations(|| {
black_box(view.conj());
});
assert_eq!(allocations, 0, "conj rank {rank} allocated");
let allocations = count_allocations(|| {
black_box(view.permute(&perm).unwrap());
});
assert_eq!(allocations, 0, "permute rank {rank} allocated");
let allocations = count_allocations(|| {
black_box(view.broadcast(&dims).unwrap());
});
assert_eq!(allocations, 0, "broadcast rank {rank} allocated");
if rank == 2 {
let (dims2, strides2, data2) = contiguous_case(2);
let view2 = StridedView::<f64>::new(&data2, &dims2, &strides2, 0).unwrap();
let allocations = count_allocations(|| {
black_box(view2.transpose_2d().unwrap());
});
assert_eq!(allocations, 0, "transpose_2d allocated");
}
if rank >= 2 {
let allocations = count_allocations(|| {
black_box(view.diagonal_view(&[(0, 1)]).unwrap());
});
assert_eq!(allocations, 0, "diagonal_view rank {rank} allocated");
}
}
}
#[test]
fn diagonal_view_with_repeated_pairs_allocates_nothing() {
let dims = [2usize, 2];
let strides = [0isize, 0];
let data = [0.0f64; 1];
let view = StridedView::<f64>::new(&data, &dims, &strides, 0).unwrap();
let pairs = [(0usize, 1usize); INLINE_RANK + 1];
let allocations = count_allocations(|| {
let diagonal = view.diagonal_view(&pairs).unwrap();
assert_eq!(diagonal.dims(), &[2]);
black_box(diagonal.dims().len());
});
assert_eq!(allocations, 0, "duplicate diagonal pairs allocated");
}
#[test]
fn mutable_permute_and_reborrow_allocate_nothing_up_to_inline_rank() {
for rank in 1..=INLINE_RANK {
let (dims, strides, data) = contiguous_case(rank);
let perm: Vec<usize> = (0..rank).rev().collect();
let mut data_mut = data.clone();
let allocations = count_allocations(|| {
let view = StridedViewMut::<f64>::new(&mut data_mut, &dims, &strides, 0)
.unwrap()
.permute(&perm)
.unwrap();
black_box(view.dims().len());
});
assert_eq!(
allocations, 0,
"StridedViewMut::permute rank {rank} allocated"
);
let view = StridedViewMut::<f64>::new(&mut data_mut, &dims, &strides, 0).unwrap();
let allocations = count_allocations(|| {
black_box(view.as_view());
});
assert_eq!(allocations, 0, "as_view rank {rank} allocated");
}
}
#[test]
fn stride_and_axis_helpers_are_inline_up_to_inline_rank() {
for rank in 1..=INLINE_RANK {
let dims = dims_for(rank);
let mut strides: Vec<isize> = Vec::new();
let allocations = count_allocations(|| {
black_box(col_major_strides(&dims));
black_box(row_major_strides(&dims));
});
assert_eq!(allocations, 0, "stride helpers rank {rank} allocated");
strides.extend(col_major_strides(&dims));
let allocations = count_allocations(|| {
black_box(index_order(&strides));
});
assert_eq!(allocations, 0, "index_order rank {rank} allocated");
}
}
#[test]
fn strided_array_metadata_is_inline() {
for rank in 1..=INLINE_RANK {
let (dims, strides, data) = contiguous_case(rank);
let allocations = count_allocations(|| {
let array =
StridedArray::from_parts(black_box(data.clone()), &dims, &strides, 0).unwrap();
black_box(array.dims().len());
});
assert_eq!(
allocations, 1,
"from_parts rank {rank} allocated beyond the element buffer"
);
let mut array = StridedArray::from_parts(data.clone(), &dims, &strides, 0).unwrap();
let allocations = count_allocations(|| {
black_box(array.view());
});
assert_eq!(allocations, 0, "StridedArray::view rank {rank} allocated");
let allocations = count_allocations(|| {
black_box(array.view_mut());
});
assert_eq!(
allocations, 0,
"StridedArray::view_mut rank {rank} allocated"
);
}
}
#[test]
fn strided_array_permuted_metadata_is_inline() {
for rank in 1..=INLINE_RANK {
let (dims, strides, data) = contiguous_case(rank);
let perm: Vec<usize> = (0..rank).rev().collect();
let owned = data.clone();
let allocations = count_allocations(move || {
let array = StridedArray::from_parts(owned, &dims, &strides, 0).unwrap();
let permuted = array.permuted(&perm).unwrap();
black_box(permuted.dims().len());
});
assert_eq!(
allocations, 0,
"StridedArray::permuted rank {rank} allocated"
);
}
}
#[test]
fn col_major_allocates_only_its_element_buffer_up_to_inline_rank() {
for rank in 1..=INLINE_RANK {
let dims = dims_for(rank);
let allocations = count_allocations(|| {
black_box(StridedArray::<f64>::col_major(&dims));
});
assert_eq!(
allocations, 1,
"StridedArray::col_major rank {rank} allocated metadata"
);
}
}
#[test]
fn above_inline_rank_spills_gracefully() {
let rank = INLINE_RANK + 1;
let (dims, strides, data) = contiguous_case(rank);
let allocations = count_allocations(|| {
let view = StridedView::<f64>::new(&data, &dims, &strides, 0).unwrap();
black_box(view.dims().len());
});
assert_eq!(allocations, 3, "rank 9 new should spill the boxed metadata");
let view = StridedView::<f64>::new(&data, &dims, &strides, 0).unwrap();
let allocations = count_allocations(|| {
black_box(view.clone());
});
assert_eq!(
allocations, 3,
"rank 9 clone should spill the boxed metadata"
);
let perm: Vec<usize> = (0..rank).rev().collect();
let allocations = count_allocations(|| {
black_box(view.permute(&perm).unwrap());
});
assert!(
allocations >= 2,
"rank 9 permute should spill, got {allocations} allocations"
);
let allocations = count_allocations(|| {
black_box(col_major_strides(&dims));
});
assert_eq!(allocations, 1, "rank 9 col_major_strides should spill once");
let allocations = count_allocations(|| {
black_box(index_order(&strides));
});
assert_eq!(allocations, 1, "rank 9 index_order should spill once");
}