Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,34 @@ There is no process wide parallelism setting. Every operation that can run in pa

```rust
faer::team::with_team(8, |par| {
// every faer call in here with `par` runs its regions on the eight reserved members
// every faer call in here with `par` runs its regions on 8 participants: the calling thread
// and 7 reserved workers
qr_in_place(a.as_mut(), h.as_mut(), par, stack, params);
});
```

A caller that already owns a team lends a sub-team to each call with `par.reborrow()`; a width of one
is `Par::Seq`. `with_team` reserves a team for the duration of the closure, so a caller that keeps a
budget across many calls should own a team itself. See the *parallelism* section of the crate
documentation for the full picture.
is `Par::Seq`.

`with_team` is for a one-off call: every call builds a new pool, so it spawns its worker threads and
joins them again when the closure ends. Code that runs parallel work repeatedly should own the
`threadteam` `Pool` and `Team` itself, build them once, and lend a `Par` from the team to each call:

```rust
// once: 8 participants, the calling thread and 7 reserved workers
let pool = threadteam::Pool::builder().threads(8).build()?;
let team = pool.into_team(8).ok().expect("an idle pool");

// per call: lend the team to faer for the duration of the closure
for (a, h) in work {
let mut exec = team.exec(8);
exec.team(|t| qr_in_place(a.as_mut(), h.as_mut(), Par::Team(t), stack, params))
.ok()
.expect("a team token for an idle team");
}
```

See the *parallelism* section of the crate documentation for the full picture.

# minimum supported rust version

Expand Down
8 changes: 7 additions & 1 deletion faer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@
//! Default::default(),
//! ));
//! faer::team::with_team(4, |par| {
//! // every faer call in here with `par` runs its regions on the four reserved members
//! // every faer call in here with `par` runs its regions on 4 participants: the calling
//! // thread and 3 reserved workers
//! faer::linalg::svd::svd(
//! a.as_ref(),
//! s.as_mut().col_mut(0).as_diagonal_mut(),
Expand All @@ -220,6 +221,11 @@
//! the members busy-wait for a bounded time between regions, then sleep; the width is the one the
//! caller asked `with_team` for, and there is no process-wide setting to override it.
//!
//! `with_team` is for a one-off call: each call builds a new pool, spawning its worker threads and
//! joining them again at the end. Code that runs parallel work repeatedly should build a
//! [`threadteam::Pool`] and [`threadteam::Team`] once and lend a `Par` from the team to each call
//! (see the example on [`team::with_team`]).
//!
//! # crate features
//!
//! - `std`: enabled by default. links with the standard library to enable
Expand Down
63 changes: 59 additions & 4 deletions faer/src/utils/team.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,49 @@ use threadteam::TeamExec;
/// let a = Mat::<f64>::from_fn(8, 8, |i, j| (i + j) as f64);
/// let mut dst = Mat::<f64>::zeros(8, 8);
/// faer::team::with_team(2, |par| {
/// // every faer call in here runs its regions on the two reserved members
/// // every faer call in here runs its regions on 2 participants: the calling thread and
/// // 1 reserved worker
/// faer::linalg::matmul::matmul(dst.as_mut(), Accum::Replace, a.as_ref(), a.as_ref(), 1.0, par);
/// });
/// ```
///
/// `width` is clamped to at least one; a width of one is [`crate::Par::Seq`] and reserves nothing.
/// The pool is the host's own ([`threadteam::Pool::builder`], `width` threads); a caller that
/// already owns a pool or a team should borrow from it directly instead, because two pools do not
/// share their members.
/// The team has `width` participants: the calling thread and `width - 1` reserved workers. The pool
/// is the host's own ([`threadteam::Pool::builder`], `width` threads); a caller that already owns a
/// pool or a team should borrow from it directly instead, because two pools do not share their
/// members.
///
/// `with_team` is for a one-off call: every call builds a new pool, so it spawns its worker threads
/// and joins them again when the closure ends. A caller that runs parallel work repeatedly should
/// own the [`threadteam::Pool`] and [`threadteam::Team`] itself, build them once, and lend a `Par`
/// from the team to each call:
///
/// ```
/// use faer::{Accum, Mat, Par};
/// let a = Mat::<f64>::from_fn(8, 8, |i, j| (i + j) as f64);
/// let mut dst = Mat::<f64>::zeros(8, 8);
///
/// // once: 4 participants, the calling thread and 3 reserved workers
/// let pool = threadteam::Pool::builder().threads(4).build().unwrap();
/// let team = pool.into_team(4).ok().expect("an idle pool");
///
/// // per call: lend the team to faer for the duration of the closure
/// for _ in 0..3 {
/// let mut exec = team.exec(4);
/// exec.team(|t| {
/// faer::linalg::matmul::matmul(
/// dst.as_mut(),
/// Accum::Replace,
/// a.as_ref(),
/// a.as_ref(),
/// 1.0,
/// Par::Team(t),
/// );
/// })
/// .ok()
/// .expect("a team token for an idle team");
/// }
/// ```
///
/// Panics if the team cannot be built or acquired: the closure's members wake asynchronously, so
/// the acquisition waits as long as the team tests do (30 s) instead of threadteam's 2 ms default,
Expand Down Expand Up @@ -726,6 +760,27 @@ mod tests {
exec.team(|team| f(Par::Team(team))).ok().expect("a team token")
}

/// `with_team(width)` hands out `width` participants: the calling thread and `width - 1`
/// reserved workers. `into_team` clamps the width to the pool's, so a pool of `width - 1`
/// threads would give one participant fewer.
#[test]
fn with_team_has_width_participants() {
for width in [2usize, 3, 4] {
let size = with_team(width, |par| match par {
Par::Team(t) => t.size().get(),
_ => 1,
});
assert!(size == width);
}
let pool = Pool::builder()
.threads(3)
.acquire_timeout(std::time::Duration::from_secs(30))
.build()
.unwrap();
let team = pool.into_team(4).ok().expect("an idle pool");
assert!(team.size().get() == 3);
}

fn rand_mat(m: usize, n: usize, seed: u64) -> Mat<f64> {
let mut x = seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
Mat::from_fn(m, n, |_, _| {
Expand Down
Loading