Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

PresetQueueAccumulateWakeUse case
Default / RobustPerWorkerOnFinalizeEveryPushAll-rounder
GraphHeavy(same as Robust)Large trees (alias for Robust)
WideLightSharedOnArrivalEveryPushbf > 10
LowOverheadPerWorkerOnFinalizeOncePerBatchNoop-sensitive
PerWorkerArrivalPerWorkerOnArrivalEveryPushStreaming + deques
SharedDefaultSharedOnFinalizeEveryPushShared baseline
HighThroughputPerWorkerOnFinalizeEveryK<4>Heavy balanced
StreamingWideSharedOnArrivalOncePerBatchKnown +11% fold-hv
DeepNarrowPerWorkerOnFinalizeEveryK<2>bf=2 chains

Decision guide

Start from the tree shape, then refine by work distribution:

%3q1Tree shape?wideWide(bf > 10)q1->widewidenarrowDeep narrow(bf = 2)q1->narrownarrowq2Work balance?q1->q2mediumwlWideLightwide->wldnDeepNarrownarrow->dndefDefault (Robust)q2->defbalanced orfinalize-heavypwaPerWorkerArrivalq2->pwainit-heavy orgraph-heavy

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);
}
}
StrategyBehaviorPer-worker state
EveryPushNotify on every push() (none)
OncePerBatchFirst push per graph.visit onlybool
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.