Policies: Configuration and Presets
The funnel’s behavior is fully determined by three compile-time axes
bundled into FunnelPolicy:
#![allow(unused)]
fn main() {
/// Bundles queue topology, accumulation strategy, and wake policy.
/// One type parameter on the executor replaces three.
pub trait FunnelPolicy: 'static {
type Queue: WorkStealing;
type Accumulate: AccumulateStrategy;
type Wake: WakeStrategy;
}
}
The Spec
#![allow(unused)]
fn main() {
pub struct Spec<P: FunnelPolicy = policy::Default> {
/// Pool size for `.run()` and `.session()`. Not consulted when
/// attaching to an explicit pool via `.attach()`.
pub default_pool_size: usize,
pub queue: <P::Queue as WorkStealing>::Spec,
pub accumulate: <P::Accumulate as AccumulateStrategy>::Spec,
pub wake: <P::Wake as WakeStrategy>::Spec,
}
}
Each axis contributes its Spec type. default_pool_size sets the
thread count for one-shot execution. Arenas grow lazily via
segmented allocation — no capacity configuration.
Named presets
#![allow(unused)]
fn main() {
// ── Named presets (type aliases) ─────────────────
//
// Each is a concrete instantiation of Policy<Q, A, W>.
// `Default` aliases the robust all-rounder; other names describe
// the workload shape they're tuned for.
/// PerWorker + OnFinalize + EveryPush. The robust all-rounder.
pub type Robust = Policy;
/// The default policy. Alias for Robust.
pub type Default = Robust;
/// Same axes as Default. Distinguished by Spec configuration (larger arenas).
pub type GraphHeavy = Robust;
/// Shared + OnArrival + EveryPush. Wide trees (bf=20+).
pub type WideLight = Policy<queue::Shared, accumulate::OnArrival>;
/// PerWorker + OnFinalize + OncePerBatch. Overhead-sensitive (noop-like).
pub type LowOverhead = Policy<queue::PerWorker, accumulate::OnFinalize, wake::OncePerBatch>;
/// PerWorker + OnArrival + EveryPush. Streaming sweep with per-worker deques.
pub type PerWorkerArrival = Policy<queue::PerWorker, accumulate::OnArrival>;
/// Shared + OnFinalize + EveryPush.
pub type SharedDefault = Policy<queue::Shared>;
/// PerWorker + OnFinalize + EveryK<4>. Balanced wakeups for heavy workloads.
pub type HighThroughput = Policy<queue::PerWorker, accumulate::OnFinalize, wake::EveryK<4>>;
/// Shared + OnArrival + OncePerBatch.
pub type StreamingWide = Policy<queue::Shared, accumulate::OnArrival, wake::OncePerBatch>;
/// PerWorker + OnFinalize + EveryK<2>. For deep narrow trees (bf=2).
pub type DeepNarrow = Policy<queue::PerWorker, accumulate::OnFinalize, wake::EveryK<2>>;
}
Nine names map to seven distinct monomorphizations:
| Preset | Queue | Accumulate | Wake | Use case |
|---|---|---|---|---|
Default / Robust | PerWorker | OnFinalize | EveryPush | All-rounder |
GraphHeavy | (same as Robust) | Large trees (alias for Robust) | ||
WideLight | Shared | OnArrival | EveryPush | bf > 10 |
LowOverhead | PerWorker | OnFinalize | OncePerBatch | Noop-sensitive |
PerWorkerArrival | PerWorker | OnArrival | EveryPush | Streaming + deques |
SharedDefault | Shared | OnFinalize | EveryPush | Shared baseline |
HighThroughput | PerWorker | OnFinalize | EveryK<4> | Heavy balanced |
StreamingWide | Shared | OnArrival | OncePerBatch | Known +11% fold-hv |
DeepNarrow | PerWorker | OnFinalize | EveryK<2> | bf=2 chains |
Decision guide
Start from the tree shape, then refine by work distribution:
When unsure, use Spec::default(n) — the Robust preset has zero
regressions on any benchmarked workload.
The three usage tiers
#![allow(unused)]
fn main() {
use hylic::prelude::*;
// One-shot: creates pool, runs, joins
exec(funnel::Spec::default(8)).run(&fold, &graph, &root);
// Session scope: pool lives for the closure, multiple folds share it
exec(funnel::Spec::default(8)).session(|s| {
s.run(&fold1, &graph1, &root1);
s.run(&fold2, &graph2, &root2);
});
// Explicit attach: manual pool management
funnel::Pool::with(8, |pool| {
exec(funnel::Spec::default(8)).attach(pool).run(&fold, &graph, &root);
});
}
See The Exec pattern for the type-level design behind these tiers.
Wake strategies
#![allow(unused)]
fn main() {
/// Wake strategy: when to notify idle workers of pushed tasks.
///
/// `State` is per-worker mutable state (embedded in WorkerCtx as
/// `Cell<State>`). Created once via `init_state`, reset per visit batch.
pub trait WakeStrategy: 'static {
type Spec: Copy + Default + Send + Sync;
type State: Copy;
fn init_state(spec: &Self::Spec) -> Self::State;
/// Called after each successful push.
/// Returns true if the caller should wake an idle worker.
fn should_notify(state: &mut Self::State) -> bool;
/// Called before each graph.visit batch.
fn reset(state: &mut Self::State);
}
}
| Strategy | Behavior | Per-worker state |
|---|---|---|
EveryPush | Notify on every push | () (none) |
OncePerBatch | First push per graph.visit only | bool |
EveryK<K> | Every K-th push (K is const generic) | u32 counter |
EveryK<K> uses a const generic — the modulus compiles to a bitmask
when K is a power of 2.
Zero-cost monomorphization
The entire call chain is generic over P: FunnelPolicy. The compiler
generates separate code per policy — WorkerCtx, worker_loop,
walk_cps, fire_cont, push_task are all monomorphized. No vtable,
no trait object, no indirect call. Each push and try_acquire is a
direct, inlinable function call.