Struct SharedMemBuffer

Source
pub struct SharedMemBuffer { /* private fields */ }
Available on crate features shared_mem or pocl_extensions only.
Expand description

Represents an MCL shared buffer, which is essentially a pointer to data which exists in shared memory. When only the shared_mem feature is turned on this buffer will exist in host shared memory only. If instead the pocl_extensions feature is used, the the buffer will also exist in device shared memory. Note that pocl_extensions requires a patched version of POCL 1.8 to have been succesfully installed (please see https://github.com/pnnl/mcl/tree/dev#using-custom-pocl-extensions for more information).

A shared buffer allows tasks within different processes and applications to use the same buffer. Further, we support creating sub-buffers of a shared buffers to alleviate some of the overhead associated with creating new buffers.

#Safety Given that Shared Buffers can be used by multiple processes simultaenously they should always be considered inherantly unsafe, as we are currently able to provide saftey gaurantees within a single process. Please see the discussion on saftey for RegisteredBuffer for details on the protections offered within a single process. Given that Shared Buffers can be used by multiple tasks and processes simultaneously, and that accelerators are often multi-threaded we try to ensure that RegisteredBuffers are safe with respect to read and write access.

While we are unable to enforce read/write saftey guarantees across processes, the MCL library does provide reference counting of the underlying shared memory buffer, and will release the resources once all references across all proceesses have been dropped.

Implementations§

Source§

impl SharedMemBuffer

Source

pub fn offset(&self) -> usize

Return the offset into the original SharedMemBuffer this handle starts at.

§Examples
     let mcl = mcl_rs::MclEnvBuilder::new().num_workers(10).initialize();
     mcl.load_prog("my_path", mcl_rs::PrgType::Src);
     let num_elems = 100;
     let buf = mcl.create_shared_buffer(mcl_rs::TaskArg::inout_shared::<u32>("my_buffer", num_elems));
     assert_eq!(buf.offset(),0);
     let sub_buf = buf.sub_buffer(10..20);
     assert_eq!(sub_buf.offset(),10);
Source

pub fn len(&self) -> usize

Return the len of this (sub)-SharedMemBuffer

§Examples
     let mcl = mcl_rs::MclEnvBuilder::new().num_workers(10).initialize();
     mcl.load_prog("my_path", mcl_rs::PrgType::Src);
     let num_elems = 100;
     let buf = mcl.create_shared_buffer(mcl_rs::TaskArg::inout_shared::<u32>("my_buffer", num_elems));
     assert_eq!(buf.len(),100);
     let sub_buf = buf.sub_buffer(10..20);
     assert_eq!(sub_buf.len(),10);
Source

pub fn try_detach(self) -> Result<(), Self>

Try to manually detach from this shared memory segment (i.e. decrement the global buffer reference count), this will only succeed if this is the last reference locally

NOTE: Dropping a handle potentially calls this automatically provided it is the last local (to this process) reference to the buffer.

§Examples
     let mcl = mcl_rs::MclEnvBuilder::new().num_workers(10).initialize();
     mcl.load_prog("my_path", mcl_rs::PrgType::Src);

     let num_elems = 100;
     let buf = mcl.create_shared_buffer(mcl_rs::TaskArg::inout_shared::<u32>("my_buffer", num_elems));
     let sub_buf = buf.sub_buffer(10..20);

     let buf = buf.try_detach();
     assert!(buf.is_err());
     let buf = buf.unwrap_err();
     drop(sub_buf);
     assert!(buf.try_detach().is_ok())
Source

pub fn sub_buffer(&self, range: impl RangeBounds<usize>) -> Self

Creates a sub buffer using the provided range from a given SharedMemBuffer The sub buffer essentially “locks” the elements in the provided range BUT ONLY ON THE CALLING PROCESS (other processes will have no idea of these locked regions) delaying other (local to this process) sub buffers from executing with overlapping elements until all references to this sub buffer have been dropped. Note that sub buffer element locking happens at task execution time rather that sub buffer handle creation. This allows overlapping sub buffers be created and passed as arguments to different tasks, with the dependecies being handled automatically based on the submission and execution order of the tasks

One can also create sub buffers of sub buffers.

§Examples
     let mcl = mcl_rs::MclEnvBuilder::new().num_workers(10).initialize();
     mcl.load_prog("my_path", mcl_rs::PrgType::Src);

     let num_elems = 100;
     let buf = mcl.create_shared_buffer(mcl_rs::TaskArg::inout_shared::<u32>("my_buffer", num_elems));
     let sub_buf1 = buf.sub_buffer(10..20);
     let sub_buf2 = buf.sub_buffer(15..25); // this call will succeed even though it overlaps with sub_buf1
     let tasks = async move {
         let pes: [u64; 3] = [1, 1, 1]
         let task_1 = mcl.task("my_kernel", 1)
                 .arg_buffer(mcl_rs::TaskArg::output_slice(sub_buf1))
                 .dev(mcl_rs::DevType::CPU)
                 .exec(pes);
         let task_1 = mcl.task("my_kernel", 1)
                 .arg_buffer(mcl_rs::TaskArg::output_slice(sub_buf1))
                 .dev(mcl_rs::DevType::CPU)
                 .exec(pes);
         /// We can even create our next task sucessfully with the overlapping buffer because no actual work occurs until we call await
         let task_2 = mcl.task("my_kernel", 1)
                 .arg_buffer(mcl_rs::TaskArg::output_slice(sub_buf1))
                 .dev(mcl_rs::DevType::CPU)
                 .exec(pes);
     }
     // drive both futures simultaneously -- based on the overlapping dependency, these task will in reality be executed serially
     // as the internal implementation will prevent both tasks from allocating the overlapping sub_buffer regions simultaneously
     futures::future::join_all([task_1,task_2]);
     futures::executor::block_on(task);
     
Source

pub unsafe fn as_slice<T>(&self) -> &[T]

Extract a T slice from this SharedMemBuffer handle.

#Saftey This is unsafe as we currently have no mechanism to guarantee the alignment of T with the alignment used to originally create the buffer potentially in a different process. The user must ensure the alignment is valid otherwise behavior is undefined.

§Examples
     let mcl = mcl_rs::MclEnvBuilder::new().num_workers(10).initialize();
     mcl.load_prog("my_path", mcl_rs::PrgType::Src);

     let num_elems = 100;
     let buf = mcl.attach_shared_buffer(mcl_rs::TaskArg::inout_shared::<u32>("my_buffer", num_elems));
     let sliced = unsafe { buf.as_slice::<u32>()};
Source

pub unsafe fn as_mut_slice<T>(&self) -> &mut [T]

Extract a T slice from this SharedMemBuffer handle.

#Saftey This is unsafe as we currently have no mechanism to guarantee the alignment of T with the alignment used to originally create the buffer potentially in a different process. The user must ensure the alignment is valid otherwise behavior is undefined.

§Examples
     let mcl = mcl_rs::MclEnvBuilder::new().num_workers(10).initialize();
     mcl.load_prog("my_path", mcl_rs::PrgType::Src);

     let num_elems = 100;
     let buf = mcl.attach_shared_buffer(mcl_rs::TaskArg::inout_shared::<u32>("my_buffer", num_elems));
     let slice = unsafe { buf.as_mut_slice::<u32>()};

Trait Implementations§

Source§

impl Clone for SharedMemBuffer

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 · Source§

const fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Drop for SharedMemBuffer

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.