pub struct PoolBuilder { /* private fields */ }Expand description
Implementations§
Source§impl PoolBuilder
impl PoolBuilder
Sourcepub fn threads(self, n: usize) -> PoolBuilder
pub fn threads(self, n: usize) -> PoolBuilder
Sourcepub fn thread_name(self, f: impl Fn(usize) -> String + 'static) -> PoolBuilder
pub fn thread_name(self, f: impl Fn(usize) -> String + 'static) -> PoolBuilder
Names the workers; the closure receives the worker index.
§Examples
let pool = threadteam::Pool::builder()
.threads(1)
.thread_name(|i| format!("my-pool-{i}"))
.build()?;Sourcepub fn stack_size(self, bytes: usize) -> PoolBuilder
pub fn stack_size(self, bytes: usize) -> PoolBuilder
Worker stack size in bytes. Defaults to 16 MiB, sized for recursive LU.
§Examples
let pool = threadteam::Pool::builder().threads(1).stack_size(4 << 20).build()?;Sourcepub fn on_start(self, f: impl Fn(usize) + Send + Sync + 'static) -> PoolBuilder
pub fn on_start(self, f: impl Fn(usize) + Send + Sync + 'static) -> PoolBuilder
Hook run on each worker when it starts, with the worker index.
Typical use is pinning the worker to a core. The hook is not called
without the parallel feature, which starts no workers.
§Examples
let pool = threadteam::Pool::builder().threads(1).on_start(|_| {}).build()?;Sourcepub fn idle_spin(self, spin: Duration) -> PoolBuilder
pub fn idle_spin(self, spin: Duration) -> PoolBuilder
Default spin time of an unreserved idle team member before it returns
its thread to Rayon. Defaults to 200 ms; zero returns it immediately.
The member also returns once it has polled for the pool’s spin budget
(spin_polls), whichever comes first.
Operations may override it with WaitOverride.
A reserved member never leaves on a timeout.
§Examples
use std::time::Duration;
let pool = threadteam::Pool::builder().threads(2).idle_spin(Duration::ZERO).build()?;
assert_eq!(pool.wait_policy().idle_spin(), Duration::ZERO);Sourcepub fn idle_runs_rayon(self, on: bool) -> PoolBuilder
pub fn idle_runs_rayon(self, on: bool) -> PoolBuilder
Default for whether unreserved idle members run Rayon jobs while they
spin. Defaults to false: native team execution keeps them for the
next reservation. true is the compatibility mode in which an idle
member serves Rayon work (an acquisition then refuses a member that is
inside such a job instead of waiting for it).
§Examples
let pool = threadteam::Pool::builder().threads(2).idle_runs_rayon(true).build()?;
assert!(pool.wait_policy().idle_runs_rayon());Sourcepub fn acquire_timeout(self, timeout: Duration) -> PoolBuilder
pub fn acquire_timeout(self, timeout: Duration) -> PoolBuilder
How long Exec::team waits for idle workers to
wake before it refuses. Defaults to 2 ms.
This bounds the wait for workers that are free; the acquisition does not wait for a worker that is running a job, it only runs out this time and refuses. Raise it on a loaded host to reduce spurious refusals.
§Examples
use std::time::Duration;
let pool = threadteam::Pool::builder()
.threads(2)
.acquire_timeout(Duration::from_millis(10))
.build()?;Sourcepub fn qos(self, qos: Qos) -> PoolBuilder
pub fn qos(self, qos: Qos) -> PoolBuilder
Scheduling class of the workers (see Qos).
Defaults to Qos::UserInitiated on macOS and to nothing elsewhere.
The calling thread’s class is not changed by this crate; an engine that
wants its leading thread raised too calls
raise_current_thread_qos once on it.
§Examples
use threadteam::{Pool, Qos};
let pool = Pool::builder().threads(1).qos(Qos::Default).build()?;Sourcepub fn dispatch_costs(self, costs: DispatchCosts) -> PoolBuilder
pub fn dispatch_costs(self, costs: DispatchCosts) -> PoolBuilder
Overrides the DispatchCosts of this pool’s tokens.
Defaults to DispatchCosts::host_default. The costs feed
Exec::dispatch_cost and everything built on
it (pays, width_for); they never change how work is placed.
§Examples
use std::num::NonZeroUsize;
use std::time::Duration;
use threadteam::{DispatchCosts, DispatchKind, Exec, HostClass, Pool, WidthCosts};
let costs = DispatchCosts::for_class(HostClass::Generic)
.with(DispatchKind::Outside, WidthCosts::flat(Duration::from_micros(7)));
let pool = Pool::builder().threads(2).dispatch_costs(costs).build()?;
let exec = Exec::new(&pool, 2)?;
// Without the `parallel` feature the pool has width 1: no section, no cost.
let expected = if pool.width().get() >= 2 { Duration::from_micros(7) } else { Duration::MAX };
assert_eq!(exec.dispatch_cost(NonZeroUsize::new(2).unwrap()), expected);Sourcepub fn pin_participants(self, on: bool) -> PoolBuilder
pub fn pin_participants(self, on: bool) -> PoolBuilder
Pins the members of a root team scope onto the calling thread’s CPU set. Off by default; Linux only, where it does something.
With it on, the leader of a root team scope
(Exec::team, or the root token of a
Team) reads its own affinity mask S with
sched_getaffinity, and every member pins itself with
sched_setaffinity onto one CPU of S: member k (participant k,
counting the leader as 0) onto the k-th set CPU of S, wrapping modulo
|S| when the team is wider than the set. S[0] is thereby left to the
leader, without enforcing it.
The leader is never pinned. Its mask is never changed, not even for
the scope: it keeps all of S while the members work. A vendor library
(OpenMP, OpenBLAS, a LAPACK provider) creates its thread team from the
thread that calls it, and a team created from a one-CPU thread silently
confines the whole provider team to that CPU — measured at 3-20x slower
dgemm and LU at 4 threads. Engines call such libraries from the
coordinating thread, so that thread must keep its CPU set.
A member saves its previous mask in its own slot and restores it
itself before it stops being reserved: before it returns to Rayon, and
before its thread serves work that is not its team’s — while it is lent
by Exec::external, and when it runs such work
itself (a member that leads a fork child and calls external). No
thread ever changes another thread’s affinity.
§What it is for
It keeps the members of a team apart. Measured on broccoli (16 threads on
16 allowed CPUs, 20 processes, tensor4all/faer#107): with the scheduler
free to place the threads, 11 of 20 processes ran a qr_factor 512 at
the one-thread time, with 92 % of the samples showing two runnable team
threads on one CPU; with the participants pinned, 0 of 20 did, and no
sample was stacked. Giving the team one spare CPU did not help (7 of 20
slow). That arm pinned every participant including the leader; this crate
leaves the leader on the caller’s whole set, which is what the rule above
requires and only relaxes the placement of participant 0.
§Assumptions and limits
- Linux only. The CPU set is a stack
cpu_set_tand the placement issched_setaffinity(0, ...). On every other target the option is accepted and does nothing. - The caller’s CPU set is the source of placement, so the caller’s
mask should belong to this team alone. Two teams whose leaders see the
same mask place their members on the same CPUs; give each team (in
practice: each process) its own CPU set (
taskset -c). - Best effort. A refused syscall (a mask larger than
cpu_set_t, a cpuset that changed under us, a CPU offlined) leaves that thread where it was; an empty mask pins nobody; a failed pin or restore never fails the scope. - A pinned member must not create a foreign thread team. A member
that calls OpenMP, OpenBLAS or any library that builds its team from
the calling thread directly inside a share confines that library’s
team to one CPU. Call it through
Exec::external, which restores the member’s mask first, or keep this option off. - Only the members of a team are placed. A token of a
Teamthat never opens a team scope (exec.for_ranges(...)directly) is not re-pinned; its members keep the placement of the last root scope (or ofPool::into_team).
Off by default, consistently with the rule above and with libgomp, libomp, OpenBLAS and faer, which leave threads unpinned unless asked.
§Examples
let pool = threadteam::Pool::builder().threads(2).pin_participants(true).build()?;
assert!(pool.pin_participants());Sourcepub fn spin_polls(self, polls: u32) -> PoolBuilder
pub fn spin_polls(self, polls: u32) -> PoolBuilder
Spin budget of a team wait, in polls of one PAUSE (YIELD on aarch64):
how long a reserved member waiting for work, a leader in a section’s
join, or a participant in a barrier spins before it yields a few times
and then sleeps until woken (docs/design/waiting.md; the shape of
libgomp’s GOMP_SPINCOUNT). A count, so a waiter that is descheduled
does not spend it while it waits for its CPU. Zero sleeps after the
yields. An oversubscribed scope, or a pool whose waiters were recently
descheduled, uses at most 100 polls. It also bounds how long an idle
member released by a team keeps polling before it returns to Rayon,
together with idle_spin.
§Examples
let pool = threadteam::Pool::builder().threads(2).spin_polls(4096).build()?;
assert_eq!(pool.spin_polls(), 4096);Sourcepub fn build(self) -> Result<Pool, Error>
pub fn build(self) -> Result<Pool, Error>
Starts the workers.
Without the parallel feature no thread is started and the pool has
width 1.
§Errors
Error::ZeroThreads when threads(0) was requested, and
Error::PoolBuild when the workers cannot be started.
§Examples
let pool = threadteam::Pool::builder().threads(2).build()?;
assert!(pool.width().get() <= 2);Trait Implementations§
Auto Trait Implementations§
impl !RefUnwindSafe for PoolBuilder
impl !Send for PoolBuilder
impl !Sync for PoolBuilder
impl !UnwindSafe for PoolBuilder
impl Freeze for PoolBuilder
impl Unpin for PoolBuilder
impl UnsafeUnpin for PoolBuilder
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<T, U> Imply<T> for U
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more