pub struct SharedMemBuffer { /* private fields */ }
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§
Sourcepub fn offset(&self) -> usize
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);
Sourcepub fn len(&self) -> usize
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);
Sourcepub fn try_detach(self) -> Result<(), Self>
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())
Sourcepub fn sub_buffer(&self, range: impl RangeBounds<usize>) -> Self
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);
Sourcepub unsafe fn as_slice<T>(&self) -> &[T]
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>()};
Sourcepub unsafe fn as_mut_slice<T>(&self) -> &mut [T]
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>()};