benchmarked against vec with runtime value started growable vector type impl for stackalloc'd memoryavec
parent
b0a52b992b
commit
c2f664c431
@ -0,0 +1,112 @@
|
|||||||
|
//! A `Vec`-like wrapper type that only allocates if a provided buffer is first exhausted.
|
||||||
|
use std::mem::{
|
||||||
|
MaybeUninit,
|
||||||
|
ManuallyDrop,
|
||||||
|
};
|
||||||
|
use std::marker::{Send, Sync, PhantomData};
|
||||||
|
use std::ops::Drop;
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct StackBuffer<T>
|
||||||
|
{
|
||||||
|
fill_ptr: usize,
|
||||||
|
buf_ptr: *mut MaybeUninit<T>,
|
||||||
|
}
|
||||||
|
impl<T> Clone for StackBuffer<T>
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self{
|
||||||
|
fill_ptr: self.fill_ptr,
|
||||||
|
buf_ptr: self.buf_ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> Copy for StackBuffer<T>{}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct HeapBuffer<T>
|
||||||
|
{
|
||||||
|
_fill_ptr: usize, // vec.len()
|
||||||
|
buf: Vec<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
union Internal<T>
|
||||||
|
{
|
||||||
|
stack: StackBuffer<T>,
|
||||||
|
heap: ManuallyDrop<HeapBuffer<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A growable vector with a backing slice that will move its elements to the heap if the slice space is exhausted.
|
||||||
|
pub struct AVec<'a, T>
|
||||||
|
{
|
||||||
|
/// max size of `inner.stack` before it's moved to `inner.heap`.
|
||||||
|
stack_sz: usize,
|
||||||
|
inner: Internal<T>,
|
||||||
|
|
||||||
|
_stack: PhantomData<&'a mut [MaybeUninit<T>]>,
|
||||||
|
}
|
||||||
|
unsafe impl<'a, T> Send for AVec<'a, T>{}
|
||||||
|
unsafe impl<'a, T> Sync for AVec<'a, T>{}
|
||||||
|
|
||||||
|
impl<'a, T> Drop for AVec<'a, T>
|
||||||
|
{
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.is_allocated() {
|
||||||
|
// All stack elements have been moved to the heap. Drop the heap buffer.
|
||||||
|
unsafe {
|
||||||
|
ManuallyDrop::drop(&mut self.inner.heap);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if std::mem::needs_drop::<T>() {
|
||||||
|
// Drop the allocated stack elements in place
|
||||||
|
unsafe {
|
||||||
|
std::ptr::drop_in_place(std::ptr::slice_from_raw_parts_mut(self.inner.stack.buf_ptr as *mut T, self.fill_ptr())); // I think this drops the elements, we don't need to loop.
|
||||||
|
/*
|
||||||
|
for x in slice::from_raw_parts_mut(self.inner.stack.buf_ptr, self.fill_ptr())
|
||||||
|
{
|
||||||
|
std::ptr::drop_in_place(x.as_mut_ptr());
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> AVec<'a, T>
|
||||||
|
{
|
||||||
|
/// The current fill_ptr of this stack buffer
|
||||||
|
fn fill_ptr(&self) -> usize
|
||||||
|
{
|
||||||
|
// SAFETY: Both fields are repr(C) with this element first
|
||||||
|
unsafe {
|
||||||
|
self.inner.stack.fill_ptr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Have the elements been moved to the heap?
|
||||||
|
pub fn is_allocated(&self) -> bool
|
||||||
|
{
|
||||||
|
self.fill_ptr() > self.stack_sz
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `AVec` with this backing buffer.
|
||||||
|
pub fn new(stack: &'a mut [MaybeUninit<T>]) -> Self
|
||||||
|
{
|
||||||
|
let (buf_ptr, stack_sz) = (stack.as_mut_ptr(), stack.len());
|
||||||
|
|
||||||
|
Self {
|
||||||
|
stack_sz,
|
||||||
|
inner: Internal {
|
||||||
|
stack: StackBuffer {
|
||||||
|
fill_ptr: 0,
|
||||||
|
buf_ptr,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_stack: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
//! Contains tests and benchmarks
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn unwinding_over_boundary()
|
||||||
|
{
|
||||||
|
super::alloca(120, |_buf| panic!());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn with_alloca()
|
||||||
|
{
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
|
|
||||||
|
const SIZE: usize = 128;
|
||||||
|
let sum = super::alloca(SIZE, |buf| {
|
||||||
|
|
||||||
|
println!("Buffer size is {}", buf.len());
|
||||||
|
for (i, x) in (1..).zip(buf.iter_mut()) {
|
||||||
|
*x = MaybeUninit::new(i as u8);
|
||||||
|
}
|
||||||
|
eprintln!("Buffer is now {:?}", unsafe { std::mem::transmute::<_, & &mut [u8]>(&buf) });
|
||||||
|
|
||||||
|
buf.iter().map(|x| unsafe { x.assume_init() } as u64).sum::<u64>()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(sum, (1..=SIZE).sum::<usize>() as u64);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn raw_trampoline()
|
||||||
|
{
|
||||||
|
use std::ffi::c_void;
|
||||||
|
|
||||||
|
let size: usize = 100;
|
||||||
|
let output = {
|
||||||
|
let mut size: usize = size;
|
||||||
|
extern "C" fn callback(ptr: *mut c_void, data: *mut c_void)
|
||||||
|
{
|
||||||
|
let size = unsafe {&mut *(data as *mut usize)};
|
||||||
|
let slice = unsafe {
|
||||||
|
std::ptr::write_bytes(ptr, 0, *size);
|
||||||
|
std::slice::from_raw_parts_mut(ptr as *mut u8, *size)
|
||||||
|
};
|
||||||
|
println!("From callback! Size is {}", slice.len());
|
||||||
|
|
||||||
|
for (i, x) in (0..).zip(slice.iter_mut())
|
||||||
|
{
|
||||||
|
*x = i as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
*size = slice.iter().map(|&x| x as usize).sum::<usize>();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
super::ffi::alloca_trampoline(size, callback, &mut size as *mut usize as *mut _);
|
||||||
|
}
|
||||||
|
size
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(output, (0..size).sum::<usize>());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(nightly)]
|
||||||
|
mod bench
|
||||||
|
{
|
||||||
|
const SIZE: usize = 1024;
|
||||||
|
use test::{black_box, Bencher};
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref SIZE_RANDOM: usize = {
|
||||||
|
use std::time;
|
||||||
|
|
||||||
|
let base = time::SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_millis() as u64;
|
||||||
|
|
||||||
|
((base & 300) + 1024) as usize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn vec_of_uninit_bytes_unknown(b: &mut Bencher)
|
||||||
|
{
|
||||||
|
let size = *SIZE_RANDOM;
|
||||||
|
b.iter(|| {
|
||||||
|
black_box(vec![MaybeUninit::<u8>::uninit(); size]);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[bench]
|
||||||
|
fn stackalloc_of_uninit_bytes_unknown(b: &mut Bencher)
|
||||||
|
{
|
||||||
|
let size = *SIZE_RANDOM;
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
black_box(crate::alloca(size, |b| {black_box(b);}));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn stackalloc_of_zeroed_bytes_unknown(b: &mut Bencher)
|
||||||
|
{
|
||||||
|
let size = *SIZE_RANDOM;
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
black_box(crate::alloca_zeroed(size, |b| {black_box(b);}));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn vec_of_zeroed_bytes_unknown(b: &mut Bencher)
|
||||||
|
{
|
||||||
|
let size = *SIZE_RANDOM;
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
black_box(vec![0u8; size]);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[bench]
|
||||||
|
fn vec_of_zeroed_bytes_known(b: &mut Bencher)
|
||||||
|
{
|
||||||
|
b.iter(|| {
|
||||||
|
black_box(vec![0u8; SIZE]);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[bench]
|
||||||
|
fn vec_of_uninit_bytes_known(b: &mut Bencher)
|
||||||
|
{
|
||||||
|
b.iter(|| {
|
||||||
|
black_box(vec![MaybeUninit::<u8>::uninit(); SIZE]);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[bench]
|
||||||
|
fn stackalloc_of_uninit_bytes_known(b: &mut Bencher)
|
||||||
|
{
|
||||||
|
b.iter(|| {
|
||||||
|
black_box(crate::alloca(SIZE, |b| {black_box(b);}));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn stackalloc_of_zeroed_bytes_known(b: &mut Bencher)
|
||||||
|
{
|
||||||
|
b.iter(|| {
|
||||||
|
black_box(crate::alloca_zeroed(SIZE, |b| {black_box(b);}));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue