Skip to content

Commit

Permalink
Add the join! macro
Browse files Browse the repository at this point in the history
The `join!` macro forks and joins many expressions at once. This is easier to
use than deeply nesting calls to `rayon::join`.
  • Loading branch information
fitzgen committed Sep 18, 2017
1 parent 4d8c9bd commit 0061b16
Showing 1 changed file with 147 additions and 0 deletions.
147 changes: 147 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,150 @@ pub use rayon_core::ThreadPool;
pub use rayon_core::join;
pub use rayon_core::{scope, Scope};
pub use rayon_core::spawn;

/// Fork and join many expressions at once.
///
/// The syntax is one or more occurrences of
///
/// ```ignore
/// let <irrefutable pattern> = fork <closure expresssion>;`.
/// ```
///
/// For example,
///
/// ```
/// #[macro_use]
/// extern crate rayon;
///
/// # fn main() {
/// join! {
/// let w = fork || 0;
/// let x = fork || 1;
/// let y = fork || 2;
/// let z = fork || 3;
/// }
///
/// assert_eq!(w, 0);
/// assert_eq!(x, 1);
/// assert_eq!(y, 2);
/// assert_eq!(z, 3);
/// # }
/// ```
///
/// This is equivalent to nesting calls to `rayon::join` like this:
///
/// ```
/// # extern crate rayon;
/// let (w, (x, (y, z))) = rayon::join(
/// || 0,
/// || rayon::join(
/// || 1,
/// || rayon::join(
/// || 2,
/// || 3,
/// )
/// )
/// );
/// ```
///
/// Alternatively, you can just get a flattened tuple of results, without
/// binding the results to any variable inside the macro.
///
/// The syntax is one or more occurrences of `<closure expression> ,` where the
/// last `,` is optional.
///
/// ```rust
/// #[macro_use]
/// extern crate rayon;
///
/// # fn main() {
/// let (w, x, y, z) = join!(|| 0, || 1, || 2, || 3);
///
/// assert_eq!(w, 0);
/// assert_eq!(x, 1);
/// assert_eq!(y, 2);
/// assert_eq!(z, 3);
/// # }
/// ```
#[macro_export]
macro_rules! join {
// Entry point for `let <pat> = fork <closure>;` usage.
( $( let $lhs:pat = fork $rhs:expr ; )+ ) => {
let join!( @left $( $lhs , )+ ) = join!( @right $( $rhs , )+ );
};

// Entry point for `<closure>,` usage.
( $x:expr $( , $xs:expr )* ) => {
join! { @flat $x $( , $xs )* }
};

// Flattening tuples with temporary variables.
( @flat $( let $lhs:ident = $rhs:expr ; )+ ) => {
{
let join!( @left $( $lhs , )+ ) = join!( @right $( $rhs , )+ );
($( $lhs ),+)
}
};
( @flat $( let $lhs:ident = $rhs:expr ; )* $x:expr $( , $xs:expr )*) => {
join! { @flat
$( let $lhs = $rhs ; )*
let lhs = $x;
$($xs),*
}
};

// Left hand side recursion to nest individual patterns into tuple patterns
// like `(x, (y, (z, ...)))`.
( @left $x:pat , ) => {
$x
};
( @left $x:pat , $( $xs:pat , )+ ) => {
( $x , join!( @left $( $xs , )+ ) )
};

// Right hand side recursion to nest exprs into rayon fork-joins

This comment has been minimized.

Copy link
@edre

edre Oct 1, 2017

Contributor

This recursion would divide work more efficiently if it divided the list in half and called the macro on each half. I don't know how easy/possible this is in the macro language though.

This comment has been minimized.

Copy link
@cuviper

cuviper Oct 1, 2017

Member

In principle, I agree, and I commented before that the interface should leave that possibility open to us. But I'm also not too worried about it, because I doubt anyone will write so many joins in one statement that scaling is a problem.

This comment has been minimized.

Copy link
@edre

edre Oct 2, 2017

Contributor

I manually implemented balanced join4 and join8 in the matmul demo as a core recursion primitive.
https://github.com/nikomatsakis/rayon/blob/master/rayon-demo/src/matmul/mod.rs#L157
A rayon-provided implementation of join5 and join9 could specifically be better than doing it yourself because it's much more likely to get the balancing right.

// like:
//
// rayon::join(
// x,
// || rayon::join(
// y,
// || rayon::join(
// z,
// || ...)))
( @right $x:expr , ) => {
($x)()
};
( @right $x:expr , $( $xs:expr , )+ ) => {
::rayon::join( $x , || join!( @right $( $xs , )+ ) )
}
}

// Necessary for the tests using macros that expand to `::rayon::whatever`.
#[cfg(test)]
mod rayon {
pub use super::*;
}

#[cfg(test)]
mod tests {
#[macro_use]
use super::*;

#[test]
fn join_macro_with_more_complex_patterns() {
struct Point(usize, usize);

join! {
let Point(w, x) = fork || Point(1, 2);
let Point(y, z) = fork || Point(3, 4);
let (((((a, _), _), _), _), _) = fork || (((((5, 4), 3), 2), 1), 0);
};

assert_eq!(w, 1);
assert_eq!(x, 2);
assert_eq!(y, 3);
assert_eq!(z, 4);
assert_eq!(a, 5);
}
}

0 comments on commit 0061b16

Please sign in to comment.