-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add KanalPtr optimization to the project
- Loading branch information
Showing
1 changed file
with
116 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
use std::cell::UnsafeCell; | ||
|
||
/// Kanal Pointer is a structure to move data efficiently between sync and async context. | ||
/// This mod transfer data with two different ways between threads: | ||
/// 1. When data size T is bigger than pointer size: | ||
/// holds pointer to that data in another side stack, and copies memory from that pointer location | ||
/// 2. When data size T is equal or less than pointer size: | ||
/// serialize data itself in pointer address, with this action KanalPtr removes one unnecessary memory load operation and improves speed. | ||
/// This structure is unsafe. KanalPtr should be pinned to memory location or be a member of pinned structure to work correctly. | ||
pub(crate) struct KanalPtr<T>(UnsafeCell<*mut T>); | ||
|
||
impl<T> Default for KanalPtr<T> { | ||
fn default() -> Self { | ||
Self(UnsafeCell::new(std::ptr::null_mut())) | ||
} | ||
} | ||
|
||
impl<T> KanalPtr<T> { | ||
/// Creates a KanalPtr from mut reference without forgeting or taking ownership | ||
/// Creator side should take care of forgeting the object after move action is completed. | ||
#[inline(always)] | ||
pub(crate) fn new_from(addr: &mut T) -> Self { | ||
if std::mem::size_of::<T>() > std::mem::size_of::<*mut T>() { | ||
Self((addr as *mut T).into()) | ||
} else { | ||
Self((store_as_kanal_ptr(addr)).into()) | ||
} | ||
} | ||
/// Creates a KanalPtr from owned object, receiver or creator should take care of dropping data inside ptr. | ||
#[inline(always)] | ||
pub(crate) fn new_owned(d: T) -> Self { | ||
if std::mem::size_of::<T>() > std::mem::size_of::<*mut T>() { | ||
panic!("bug: data can't be stored when size of T is bigger than pointer size"); | ||
} else { | ||
let ret = Self((store_as_kanal_ptr(&d)).into()); | ||
std::mem::forget(d); | ||
ret | ||
} | ||
} | ||
/// Creates a KanalPtr only for write operation, so it does not load data inside addr in KanalPtr as it is unnecessary | ||
#[inline(always)] | ||
pub(crate) fn new_write_address_ptr(addr: *mut T) -> Self { | ||
if std::mem::size_of::<T>() > std::mem::size_of::<*mut T>() { | ||
Self((addr).into()) | ||
} else { | ||
Self((std::ptr::null_mut() as *mut T).into()) | ||
} | ||
} | ||
/// Creates a KanalPtr without checking or transforming the pointer to correct KanalPtr format, | ||
/// Caller should take uf being sure that provided address is in correct KanalPtr format | ||
#[inline(always)] | ||
pub(crate) fn new_unchecked(addr: *mut T) -> Self { | ||
Self((addr).into()) | ||
} | ||
/// Reads data based on movement protocol of KanalPtr based on size of T | ||
#[inline(always)] | ||
pub(crate) unsafe fn read(&self) -> T { | ||
if std::mem::size_of::<T>() > std::mem::size_of::<*mut T>() { | ||
// Data is actual pointer location | ||
read_ptr(*self.0.get()) | ||
} else if std::mem::size_of::<T>() > 0 { | ||
// Data is serialized as pointer location, load it from pointer value instead | ||
std::mem::transmute_copy(&*self.0.get()) | ||
} else { | ||
// Data is zero sized, return std::mem::zeroed instead of doing any unnecessary action | ||
std::mem::zeroed() | ||
} | ||
} | ||
/// Writes data based on movement protocol of KanalPtr based on size of T | ||
#[inline(always)] | ||
pub(crate) unsafe fn write(&self, d: T) { | ||
if std::mem::size_of::<T>() > std::mem::size_of::<*mut T>() { | ||
// Data cant be stored as pointer value, move it to pointer location | ||
move_to_ptr(*self.0.get(), d); | ||
} else { | ||
// Data size is less or equal to pointer size, serialize data as pointer address | ||
*self.0.get() = store_as_kanal_ptr(&d); | ||
std::mem::forget(d); | ||
} | ||
} | ||
} | ||
|
||
/// Reads the pointer value for types with a size bigger than zero, in the case of zero-sized types returns std::mem::zeroed | ||
#[inline(always)] | ||
unsafe fn read_ptr<T>(ptr: *const T) -> T { | ||
if std::mem::size_of::<T>() > 0 { | ||
std::ptr::read(ptr) | ||
} else { | ||
// for zero types | ||
std::mem::zeroed() | ||
} | ||
} | ||
|
||
/// moves data to ptr location, ptr can be invalid memory location if type is zero-sized | ||
#[inline(always)] | ||
unsafe fn move_to_ptr<T>(ptr: *mut T, d: T) { | ||
if std::mem::size_of::<T>() > 0 { | ||
std::ptr::write(ptr, d); | ||
} else { | ||
std::mem::forget(d); | ||
} | ||
} | ||
|
||
/// this function stores data inside ptr in correct protocol format of KanalPtr for T types that are smaller than pointer size | ||
#[inline(always)] | ||
fn store_as_kanal_ptr<T>(ptr: *const T) -> *mut T { | ||
let mut ret: *mut T = std::ptr::null_mut(); | ||
unsafe { | ||
if std::mem::align_of::<T>() > std::mem::align_of::<*mut T>() { | ||
std::ptr::write_unaligned(&mut ret as *mut *mut T as *mut T, std::ptr::read(ptr)); | ||
} else { | ||
std::ptr::write(&mut ret as *mut *mut T as *mut T, std::ptr::read(ptr)); | ||
} | ||
} | ||
ret | ||
} |