[rust-dev] "Sendable References" for data-parallel fork/join-style algorithms

Sebastian Gesemann s.gesemann at gmail.com
Fri Jul 18 05:45:03 PDT 2014


Hi!

I was thinking about fork/join-style parallelism and about whether
this can be made to work including the possibility to pass references
(or something similar to references) across task boundaries. So far, I
came up with a little low-level building block that could be of
interest to the community. This basically allows to send
reference-like things across task boundaries without any dangling
pointer issues (I believe).

A use case I had in mind was a divide-and-conquer algorithm where the
recursion could benefit from concurrency because the division produces
independent problems operating on non-overlapping mut slices. In my
case, I tried to parallelize a multibody simulation with this. But you
could also think about a parallelized quicksort if you want. After
partitioning, sorting the two halves can be done independently and
concurrently. But you may wish to avoid splitting one vector into two
and joining them together. Instead you may want different tasks to
work on the same vector but restricted to their non-overlapping
sub-slices.

The basic idea is to erase the lifetime parameter (to make the
reference-like objects sendable) and to keep it safe w.r.t. to
lifetimes by creating a new scope that won't be left by execution
until all the borrowed and life-time erased pseudo-references are
gone. This is done by reference counting and signal/wait on a mutex.

Let me just show you for now how this can be used:

    trait IntoSendBorrow<Out> {
        unsafe fn into_sendable(self) -> Out;
        fn sendable<U>(self, func:|Out|->U) -> U { ... }
    }

    fn partition_inplace<'a>(s: &'a mut[int]) -> (&'a mut[int],&'a mut[int]) {
        ...
    }

    fn main() {
        let mut vec = Vec::from_fn(10,|u|-(u as int));

        partition_inplace(vec.as_mut_slice()).sendable(|(left,right)|{
            // Here, left and right are "sendable slices": SendMutSlice<int>
            // A "sendable reference" internally refers to a reference counter
            // stored in a mutex and increases/decreases it on clone/drop.
            // If the counter reaches zero, it will send a signal.
            spawn(proc() {
                let mut left = left; // needed for unique borrowing
                left.as_mut_slice().sort();
            });
            let mut right = right; // needed for unique borrowing
            right.as_mut_slice().sort();

        }); // <-- in case the reference counter is not null, sendable waits
            // for the signal and blocks until until it reaches zero.
            // This is done within a destructor of a function-local object
            // that carries the reference counter as a member.

        for i in vec.iter() {
            println!("{}", i)
        }
    }

IntoSendBorrow is simply implemented for all reference-like things
(&T, &mut T, &[T], &mut[T] and tuples of these).

I'm planning on putting the whole source code online on github. Feel
free to comment -- especially if you find this useful. :)

One thing I learned is that it does not seem to be possible to emulate
the behaviour of mutable references and mutable slices perfectly in
the sense that they could be uniquely borrowed without being mutable.
I think, I would have to write something like this:

    impl<T> SendMutSlice<T> {
        ...
        fn get<'a>(&'a uniq self, index: uint) -> &'a mut T
        ...
    }

where self is only uniquely borrowed but does not have to be mutable.
At least a unique borrow is needed to avoid returning mutable aliases.
Since this is not possible right now, I have to put a "mut" there
instead and that's why I need the lines

    let mut left = left;
    let mut right = right;

in the above example.

Cheers!
sg


More information about the Rust-dev mailing list