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

Benchmark results

Wall-clock means from criterion across four harnesses. Overhead pits Fused against handrolled single-threaded recursions to measure framework cost. Matrix runs Funnel across its 16 policy variants alongside Rayon and a scoped pool, all parallel, on 14 workload scenarios. Module simulation runs a synthetic dependency-graph resolver. Quick is a development-iteration subset of the Matrix grid.

Interactive: Funnel axes viewer

A per-cell view of the Matrix data, all values normalised to the real.rayon baseline (positive = slower than rayon, negative = faster). Three axis toggles fold groups of Funnel variants together; per-workload toggles include or exclude each scenario from the aggregated summaries. noop is excluded by default — the cell takes sub-millisecond and percentage deltas distort relative to scenarios with non-trivial work.

Toggle a scenario off if its shape is not representative of the workload you care about; toggle a policy axis to marginalise it and see how the other two co-vary. The panels below the table recompute as the selection changes: per-axis mean deviation, pairwise axis interaction effects, and per-workload best/worst Funnel preset.

Loading...

Matrix

Sixteen Funnel policy variants × 14 workload scenarios, plus handrolled Rayon and a scoped-pool baseline. Each cell shows the wall-clock mean and the +X% deviation from the row’s fastest entry; the row winner is marked (best).

make -C hylic-benchmark bench-matrix
Loading...

Module simulation

A synthetic dependency-graph resolver: eight workloads on two axes, sparse vs dense graph and fast vs slow per-node work. The _fast rows isolate scheduling cost; the _slow rows let per-node work dominate so runners converge.

make -C hylic-benchmark bench-modsim
Loading...

Overhead

Sequential framework cost: Fused against a handrolled fn rec(n) -> R { … } over the same workloads, plus a few parallel runners (real.rayon, hylic-rayon) for cross-reference. hylic-rayon versus real.rayon is the apples-to-apples pair for the parallel framework tax.

make -C hylic-benchmark bench-overhead
Loading...

Quick

A subset for development iteration: five runners (real.rayon plus four Funnel presets covering the queue × accumulation axes), nine workloads chosen for variation. The -ab form runs the same bench across multiple git revisions of hylic and archives results with a timestamp.

make bench-quick-light
Loading...

What the numbers say

Fused lands close to the handrolled sequential baselines — within compiler-noise margins on the Overhead rows rather than integer multiples — so the closure-based fold/treeish surface is not paying for itself in single-threaded performance. On the parallel side, no single Funnel preset wins across the Matrix: shallow-wide workloads tend to favour shared queues with arrival delivery, deep-narrow workloads tend to favour per-worker deques with finalize-buffering, and the wake axis can move a row by itself. The viewer above is the practical way to find the preset for a particular workload shape; the row-by-row reading of the Matrix table is the same information without the marginalisation tools.

The Module-simulation harness picks at the same trade in a more focused frame: the _fast rows show what scheduling decides when per-node work is cheap (the dependency-resolver case); the _slow rows show that scheduling stops mattering once the work itself dominates.

The implementation that produces these properties — policies as generic parameters on Funnel<P> so each preset compiles to its own walk, defunctionalised continuations in scoped arenas, no per-step heap traffic — is documented in the Funnel deep-dive. The numbers above are observations of the result, not derivations of the design.

Workload scenarios

Each scenario is a TreeSpec (node count, branching factor) and a WorkSpec (per-phase CPU burn amounts plus an optional I/O spin-wait). busy_work is a deterministic u64 LCG loop inside black_box; spin_wait_us is a wall-clock busy-wait. The scenarios cover a shape space — shallow-wide, deep-narrow, accumulate-heavy, finalize-heavy, I/O-bound, graph-discovery — not any specific production workload.

#![allow(unused)]
fn main() {
pub fn all_scenarios(scale: Scale) -> Vec<ScenarioDef> {
    let (n, n_large) = match scale {
        Scale::Small => (200, 500),
        Scale::Large => (2000, 5000),
    };
    vec![
        def("noop",         "noop",     TreeSpec { node_count: n, branch_factor: 8 },  w(0, 0, 0, 0, 0)),
        def("hashtable",    "hash",     TreeSpec { node_count: n, branch_factor: 8 },  w(5_000, 1_000, 0, 5_000, 0)),
        def("parse-light",  "parse-lt", TreeSpec { node_count: n, branch_factor: 8 },  w(50_000, 5_000, 5_000, 10_000, 0)),
        def("parse-heavy",  "parse-hv", TreeSpec { node_count: n, branch_factor: 8 },  w(200_000, 10_000, 10_000, 50_000, 0)),
        def("aggregate",    "aggr",     TreeSpec { node_count: n, branch_factor: 8 },  w(5_000, 100_000, 5_000, 5_000, 0)),
        def("transform",    "xform",    TreeSpec { node_count: n, branch_factor: 8 },  w(5_000, 5_000, 100_000, 5_000, 0)),
        def("finalize-only","fin",      TreeSpec { node_count: n, branch_factor: 8 },  w(0, 0, 100_000, 0, 0)),
        def("balanced",     "bal",      TreeSpec { node_count: n, branch_factor: 8 },  w(50_000, 50_000, 50_000, 50_000, 0)),
        def("io-bound",     "io",       TreeSpec { node_count: n, branch_factor: 8 },  w(5_000, 0, 0, 0, 200)),
        def("wide-shallow", "wide",     TreeSpec { node_count: n, branch_factor: 20 }, w(50_000, 10_000, 10_000, 10_000, 0)),
        def("deep-narrow",  "deep",     TreeSpec { node_count: n, branch_factor: 2 },  w(50_000, 10_000, 10_000, 10_000, 0)),
        def("large-dense",  "lg-dense", TreeSpec { node_count: n_large, branch_factor: 10 }, w(50_000, 10_000, 10_000, 10_000, 0)),
        // Graph-heavy: edge discovery is the dominant cost (like module resolution).
        // Light dict-merge accumulate. Models dependency graph traversal.
        def("graph-heavy",  "graph-hv", TreeSpec { node_count: n, branch_factor: 8 },  w(5_000, 10_000, 5_000, 200_000, 0)),
        // Graph-heavy with I/O: each edge has latency (like filesystem lookup).
        def("graph-io",     "graph-io", TreeSpec { node_count: n, branch_factor: 8 },  w(5_000, 10_000, 5_000, 50_000, 200)),
    ]
}
}
#![allow(unused)]
fn main() {
/// Per-phase work distribution.
#[derive(Clone)]
pub struct WorkSpec {
    pub init_work: u64,
    pub accumulate_work: u64,
    pub finalize_work: u64,
    pub graph_work: u64,
    pub graph_io_us: u64,
}

impl WorkSpec {
    /// Execute init-phase work. Returns a seed value.
    pub fn do_init(&self) -> u64 {
        if self.init_work > 0 { busy_work(self.init_work) } else { 0 }
    }

    /// Execute accumulate-phase work.
    pub fn do_accumulate(&self, heap: &mut u64, child: &u64) {
        if self.accumulate_work > 0 {
            *heap = heap.wrapping_add(busy_work(self.accumulate_work));
        }
        *heap = heap.wrapping_add(*child);
    }

    /// Execute finalize-phase work.
    pub fn do_finalize(&self, heap: &u64) -> u64 {
        if self.finalize_work > 0 {
            heap.wrapping_add(busy_work(self.finalize_work))
        } else {
            *heap
        }
    }

    /// Execute graph-traversal work (child discovery).
    pub fn do_graph(&self) {
        spin_wait_us(self.graph_io_us);
        if self.graph_work > 0 { black_box(busy_work(self.graph_work)); }
    }
}
}

Funnel policy variants

#![allow(unused)]
fn main() {
impl FunnelSpecs {
    pub fn new(nw: usize) -> Self {
        use funnel::wake::once_per_batch::OncePerBatchSpec;
        use funnel::wake::every_k::EveryKSpec;

        FunnelSpecs {
            // PW + Final × wake
            pw_fin:         funnel::Spec::default(nw),
            pw_fin_batch:   funnel::Spec::for_low_overhead(nw),
            pw_fin_k4:      funnel::Spec::for_high_throughput(nw),
            pw_fin_k2:      funnel::Spec::for_deep_narrow(nw),
            // PW + Arrive × wake
            pw_arrv:        funnel::Spec::for_perworker_arrival(nw),
            pw_arrv_batch:  funnel::Spec::for_perworker_arrival(nw).with_wake::<wake::OncePerBatch>(OncePerBatchSpec),
            pw_arrv_k4:     funnel::Spec::for_perworker_arrival(nw).with_wake::<wake::EveryK<4>>(EveryKSpec),
            pw_arrv_k2:     funnel::Spec::for_perworker_arrival(nw).with_wake::<wake::EveryK<2>>(EveryKSpec),
            // SH + Final × wake
            sh_fin:         funnel::Spec::for_shared_default(nw),
            sh_fin_batch:   funnel::Spec::for_shared_default(nw).with_wake::<wake::OncePerBatch>(OncePerBatchSpec),
            sh_fin_k4:      funnel::Spec::for_shared_default(nw).with_wake::<wake::EveryK<4>>(EveryKSpec),
            sh_fin_k2:      funnel::Spec::for_shared_default(nw).with_wake::<wake::EveryK<2>>(EveryKSpec),
            // SH + Arrive × wake
            sh_arrv:        funnel::Spec::for_wide_light(nw),
            sh_arrv_batch:  funnel::Spec::for_streaming_wide(nw),
            sh_arrv_k4:     funnel::Spec::for_wide_light(nw).with_wake::<wake::EveryK<4>>(EveryKSpec),
            sh_arrv_k2:     funnel::Spec::for_wide_light(nw).with_wake::<wake::EveryK<2>>(EveryKSpec),
        }
    }
}
}

See Funnel policies for the meaning of each axis, the rationale, and guidance on selecting a preset.

Text tables

Overhead
workload                              fused          hand.seq          real.seq
-------------------------------------------------------------------------------
aggr_sm                      42.9ms  (best)      43.7ms (+2%)    42.9ms  (best)
bal_sm                         76.3ms (+2%)    75.0ms  (best)    74.9ms  (best)
deep_sm                      29.9ms  (best)      30.7ms (+3%)      30.4ms (+2%)
fin_sm                       37.6ms  (best)    37.5ms  (best)      38.3ms (+2%)
graph-hv_sm                  81.9ms  (best)      83.5ms (+2%)      82.4ms (+1%)
graph-io_sm                  66.7ms  (best)      66.8ms (+1%)    66.3ms  (best)
hash_sm                         4.1ms (+1%)     4.0ms  (best)       4.1ms (+1%)
io_sm                        42.0ms  (best)    41.9ms  (best)    42.0ms  (best)
lg-dense_sm                    76.5ms (+2%)      75.3ms (+1%)    74.8ms  (best)
noop_sm                       0.0ms (+112%)     0.0ms  (best)     0.0ms (+1018%)
parse-hv_sm                   102.8ms (+2%)     102.1ms (+1%)   100.7ms  (best)
parse-lt_sm                  26.7ms  (best)      27.4ms (+2%)      27.2ms (+2%)
wide_sm                      30.5ms  (best)    30.4ms  (best)    30.5ms  (best)
xform_sm                       44.6ms (+3%)    43.4ms  (best)      43.7ms (+1%)
Matrix
workload                         hand.rayon        real.rayon             rayonfunnel.pw.arrv.pushfunnel.pw.arrv.batch funnel.pw.arrv.k4 funnel.pw.arrv.k2funnel.sh.arrv.pushfunnel.sh.arrv.batch funnel.sh.arrv.k4 funnel.sh.arrv.k2funnel.pw.fin.pushfunnel.pw.fin.batch  funnel.pw.fin.k4  funnel.pw.fin.k2funnel.sh.fin.pushfunnel.sh.fin.batch  funnel.sh.fin.k4  funnel.sh.fin.k2
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
aggr_sm                       13.7ms (+23%)     13.8ms (+24%)     13.1ms (+17%)     12.5ms (+12%)     12.5ms (+12%)     14.2ms (+27%)     12.8ms (+15%)     13.8ms (+23%)     15.8ms (+41%)     14.0ms (+25%)     15.3ms (+37%)      11.8ms (+6%)     13.4ms (+20%)      12.0ms (+8%)     13.3ms (+19%)      11.4ms (+2%)    11.2ms  (best)      12.2ms (+9%)     12.6ms (+13%)
bal_sm                         15.9ms (+2%)     18.2ms (+17%)    15.6ms  (best)     18.3ms (+17%)     18.8ms (+21%)     18.6ms (+20%)     18.5ms (+19%)     17.9ms (+15%)      16.2ms (+4%)      17.0ms (+9%)     18.1ms (+16%)     18.5ms (+19%)     18.9ms (+21%)     18.1ms (+16%)      17.0ms (+9%)      16.9ms (+9%)     18.8ms (+21%)     18.8ms (+21%)     18.6ms (+20%)
deep_sm                         7.2ms (+4%)       7.2ms (+4%)       7.2ms (+4%)       7.2ms (+4%)       7.2ms (+4%)       7.2ms (+4%)       7.2ms (+4%)     6.9ms  (best)     6.9ms  (best)     6.9ms  (best)     6.9ms  (best)       7.3ms (+6%)       7.3ms (+5%)       7.2ms (+5%)       7.2ms (+5%)       7.0ms (+1%)       7.0ms (+2%)       7.0ms (+2%)       7.0ms (+2%)
fin_sm                          8.7ms (+6%)       8.7ms (+6%)     8.2ms  (best)       8.5ms (+4%)       8.6ms (+4%)       8.6ms (+4%)       8.6ms (+4%)       8.5ms (+4%)       8.6ms (+4%)       8.5ms (+4%)       8.6ms (+4%)       8.6ms (+4%)       8.6ms (+4%)       8.6ms (+4%)       8.6ms (+4%)       8.6ms (+4%)       8.5ms (+4%)       8.6ms (+4%)       8.5ms (+4%)
graph-hv_sm                  17.7ms  (best)      17.8ms (+1%)      17.8ms (+1%)      18.0ms (+2%)     19.9ms (+13%)     20.4ms (+16%)      17.9ms (+1%)     20.4ms (+15%)      18.1ms (+2%)     19.8ms (+12%)      18.1ms (+2%)      18.1ms (+2%)     20.3ms (+15%)     21.2ms (+20%)      18.0ms (+2%)      18.1ms (+3%)     20.6ms (+16%)      18.5ms (+5%)     20.4ms (+15%)
graph-io_sm                    11.4ms (+1%)     12.5ms (+10%)      11.5ms (+1%)      11.4ms (+1%)     12.9ms (+14%)      12.3ms (+8%)      12.1ms (+7%)    11.3ms  (best)     12.6ms (+12%)      11.9ms (+5%)    11.4ms  (best)      11.6ms (+2%)      11.9ms (+5%)     12.9ms (+14%)      12.1ms (+7%)      11.5ms (+2%)     13.0ms (+15%)      12.2ms (+8%)      11.6ms (+2%)
hash_sm                         1.0ms (+3%)       1.0ms (+3%)       1.0ms (+6%)       1.0ms (+1%)       1.0ms (+2%)     0.9ms  (best)       0.9ms (+1%)     0.9ms  (best)       1.0ms (+4%)       1.0ms (+3%)       1.0ms (+3%)       1.0ms (+6%)       1.0ms (+8%)       1.0ms (+6%)       1.0ms (+6%)       1.0ms (+7%)       1.0ms (+8%)       1.0ms (+5%)       1.0ms (+6%)
io_sm                         5.8ms  (best)     5.8ms  (best)     5.8ms  (best)       5.8ms (+1%)       6.2ms (+7%)       6.0ms (+4%)       6.0ms (+4%)     5.8ms  (best)       6.2ms (+7%)       6.0ms (+4%)       6.0ms (+4%)     5.8ms  (best)       6.2ms (+7%)       6.0ms (+4%)       6.0ms (+4%)       5.8ms (+1%)       6.2ms (+7%)       6.0ms (+4%)       6.0ms (+4%)
lg-dense_sm                    16.1ms (+6%)      16.1ms (+6%)      16.1ms (+6%)      16.2ms (+7%)      16.4ms (+8%)      16.3ms (+7%)      16.4ms (+8%)      16.1ms (+6%)      16.3ms (+8%)      16.2ms (+6%)      16.2ms (+7%)     19.1ms (+26%)    15.2ms  (best)     19.1ms (+26%)      16.4ms (+8%)     18.8ms (+24%)      15.4ms (+2%)      16.3ms (+7%)      16.3ms (+7%)
noop_sm                       0.0ms  (best)     0.0ms  (best)     0.0ms  (best)     0.1ms (+102%)      0.0ms (+75%)      0.0ms (+69%)      0.0ms (+91%)     0.1ms (+168%)     0.1ms (+152%)     0.1ms (+157%)     0.1ms (+158%)     0.1ms (+109%)      0.0ms (+72%)      0.0ms (+77%)      0.0ms (+89%)     0.1ms (+167%)     0.1ms (+163%)     0.1ms (+162%)     0.1ms (+228%)
parse-hv_sm                   21.7ms (+10%)      20.5ms (+4%)     21.8ms (+10%)     21.9ms (+11%)    19.7ms  (best)     22.4ms (+13%)     22.1ms (+12%)     21.8ms (+10%)     21.8ms (+10%)      20.7ms (+5%)      20.6ms (+4%)      20.6ms (+4%)      21.2ms (+8%)      21.0ms (+6%)     22.2ms (+13%)     22.0ms (+12%)     22.8ms (+16%)     22.4ms (+13%)     22.2ms (+12%)
parse-lt_sm                    5.9ms (+20%)      5.9ms (+20%)      5.9ms (+20%)      5.9ms (+20%)      6.1ms (+24%)      6.1ms (+23%)      6.0ms (+22%)      5.8ms (+19%)      6.0ms (+22%)     4.9ms  (best)      5.5ms (+12%)      5.7ms (+16%)      5.8ms (+18%)      5.8ms (+18%)      5.7ms (+17%)      6.0ms (+22%)      6.1ms (+25%)      6.1ms (+24%)      6.1ms (+23%)
wide_sm                       6.6ms  (best)      7.8ms (+18%)      7.8ms (+18%)       6.8ms (+2%)      8.3ms (+25%)       7.0ms (+7%)       6.8ms (+2%)       6.8ms (+4%)       7.1ms (+8%)       6.9ms (+4%)       6.8ms (+4%)      7.3ms (+11%)      8.1ms (+23%)      7.3ms (+10%)      7.3ms (+11%)      7.4ms (+13%)      7.6ms (+15%)      7.4ms (+12%)      7.4ms (+12%)
xform_sm                      9.1ms  (best)       9.3ms (+2%)       9.4ms (+3%)       9.3ms (+2%)       9.6ms (+5%)       9.9ms (+8%)       9.9ms (+8%)       9.7ms (+7%)       9.8ms (+7%)       9.8ms (+7%)       9.7ms (+7%)       9.9ms (+9%)       9.9ms (+9%)       9.9ms (+9%)       9.9ms (+9%)       9.9ms (+8%)       9.9ms (+9%)       9.9ms (+9%)       9.9ms (+8%)
Module simulation
workload                      vanilla.rayon             rayonfunnel.pw.arrv.pushfunnel.pw.arrv.batch funnel.pw.arrv.k4 funnel.pw.arrv.k2funnel.sh.arrv.pushfunnel.sh.arrv.batch funnel.sh.arrv.k4 funnel.sh.arrv.k2funnel.pw.fin.pushfunnel.pw.fin.batch  funnel.pw.fin.k4  funnel.pw.fin.k2funnel.sh.fin.pushfunnel.sh.fin.batch  funnel.sh.fin.k4  funnel.sh.fin.k2
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
large-dense_fast               1.2ms (+15%)      1.2ms (+12%)      1.2ms (+13%)      1.2ms (+15%)       1.1ms (+6%)       1.1ms (+7%)      1.2ms (+11%)      1.2ms (+13%)      1.2ms (+12%)      1.2ms (+11%)      1.2ms (+16%)      1.2ms (+17%)      1.2ms (+17%)       1.1ms (+3%)     1.0ms  (best)      1.2ms (+18%)      1.2ms (+14%)      1.2ms (+15%)
large-dense_slow              22.0ms (+10%)     21.9ms (+10%)     22.2ms (+11%)      21.7ms (+8%)     22.7ms (+14%)      21.9ms (+9%)    20.0ms  (best)     22.9ms (+15%)      21.3ms (+6%)     22.4ms (+12%)     22.2ms (+11%)      21.4ms (+7%)     22.8ms (+14%)      21.7ms (+9%)      21.2ms (+6%)     22.0ms (+10%)     22.0ms (+10%)      20.5ms (+3%)
large-sparse_fast              1.2ms (+21%)     1.0ms  (best)      1.2ms (+20%)      1.2ms (+21%)      1.2ms (+21%)      1.2ms (+20%)      1.2ms (+20%)       1.0ms (+6%)      1.2ms (+20%)      1.2ms (+19%)      1.2ms (+21%)      1.2ms (+23%)      1.1ms (+10%)      1.2ms (+21%)      1.2ms (+20%)      1.2ms (+21%)      1.2ms (+22%)      1.2ms (+20%)
large-sparse_slow            18.4ms  (best)     21.0ms (+14%)     22.8ms (+24%)     21.7ms (+18%)     23.2ms (+26%)     21.7ms (+18%)     20.7ms (+12%)     23.0ms (+25%)     21.8ms (+18%)     22.0ms (+20%)     22.8ms (+24%)     21.0ms (+14%)     23.2ms (+26%)     21.4ms (+16%)     22.6ms (+23%)     22.1ms (+20%)     22.2ms (+21%)     22.4ms (+22%)
small-dense_fast               0.4ms (+38%)      0.4ms (+35%)      0.4ms (+31%)      0.3ms (+17%)      0.4ms (+38%)      0.4ms (+18%)      0.3ms (+14%)      0.4ms (+44%)     0.3ms  (best)      0.4ms (+28%)      0.4ms (+34%)      0.4ms (+32%)      0.4ms (+41%)      0.4ms (+22%)      0.4ms (+18%)      0.4ms (+39%)      0.4ms (+35%)      0.4ms (+33%)
small-dense_slow                6.0ms (+4%)       6.0ms (+3%)       5.8ms (+1%)      8.0ms (+38%)       6.2ms (+7%)       5.9ms (+3%)     5.8ms  (best)      7.1ms (+24%)       6.1ms (+6%)       5.9ms (+2%)       5.9ms (+2%)      7.6ms (+32%)       6.2ms (+7%)       6.0ms (+4%)       5.9ms (+2%)      7.3ms (+27%)       6.2ms (+7%)       6.0ms (+4%)
small-sparse_fast              0.4ms (+50%)      0.4ms (+34%)      0.4ms (+49%)      0.4ms (+28%)      0.4ms (+50%)      0.4ms (+49%)     0.3ms  (best)      0.4ms (+49%)      0.4ms (+34%)      0.4ms (+33%)      0.4ms (+43%)      0.3ms (+27%)      0.4ms (+30%)      0.4ms (+53%)      0.3ms (+27%)      0.3ms (+27%)      0.4ms (+50%)      0.4ms (+32%)
small-sparse_slow              6.6ms (+11%)     6.0ms  (best)       6.5ms (+9%)      6.5ms (+10%)      6.5ms (+10%)       6.5ms (+9%)       6.1ms (+2%)       6.5ms (+9%)       6.5ms (+9%)       6.1ms (+2%)       6.5ms (+9%)      6.6ms (+10%)      6.6ms (+10%)       6.5ms (+9%)       6.1ms (+2%)       6.5ms (+9%)       6.5ms (+9%)       6.1ms (+2%)
Quick
workload                         real.rayon funnel.pw.arrv.k4 funnel.sh.arrv.k4  funnel.pw.fin.k4  funnel.sh.fin.k4
-------------------------------------------------------------------------------------------------------------------
aggr_sm                        13.4ms (+4%)      13.9ms (+8%)     15.6ms (+21%)      13.0ms (+2%)    12.8ms  (best)
bal_sm                         18.8ms (+2%)      18.7ms (+1%)      18.9ms (+2%)    18.5ms  (best)    18.4ms  (best)
graph-hv_sm                  18.6ms  (best)      18.8ms (+1%)    18.7ms  (best)      18.9ms (+2%)      18.7ms (+1%)
hash_sm                         1.1ms (+3%)     1.0ms  (best)     1.0ms  (best)       1.1ms (+1%)       1.1ms (+2%)
noop_sm                       0.0ms  (best)      0.0ms (+87%)     0.1ms (+184%)      0.0ms (+83%)     0.1ms (+183%)
parse-hv_sm                    22.5ms (+2%)    22.1ms  (best)    22.0ms  (best)    22.1ms  (best)    22.0ms  (best)
parse-lt_sm                     6.3ms (+2%)       6.2ms (+1%)     6.1ms  (best)       6.2ms (+2%)     6.2ms  (best)
wide_sm                         7.9ms (+7%)     7.4ms  (best)     7.4ms  (best)       7.5ms (+2%)       7.5ms (+2%)
xform_sm                       10.5ms (+4%)      10.2ms (+1%)    10.1ms  (best)      10.3ms (+1%)    10.2ms  (best)

Benchmark source

Overhead harness
#![allow(unused)]
fn main() {
//! Overhead — framework cost.
//! Fused executor vs handrolled recursive baselines. No parallelism.
//! Reproduce: make -C hylic-benchmark _bench-overhead

#[path = "support/mod.rs"]
mod support;

use criterion::{criterion_group, criterion_main, Criterion};
use std::hint::black_box;
use support::scenario::{self, Scale, PreparedScenario};
use support::{baselines, bench_cell};

fn bench_overhead(c: &mut Criterion) {
    let mut group = c.benchmark_group("overhead");

    for def in scenario::all_scenarios(Scale::from_env()) {
        let s = PreparedScenario::from_def(&def, "sm");
        let p = s.as_problem();
        let all = vec![
            baselines::fused(&p),
            baselines::hand_seq(&s),
            baselines::real_seq(&s),
        ];
        for r in &all {
            bench_cell(&mut group, r.name, &p.name,
                |b, _| b.iter(|| black_box((r.run)())),
            );
        }
    }

    group.finish();
}

criterion_group!(benches, bench_overhead);
criterion_main!(benches);
}
Matrix harness
#![allow(unused)]
fn main() {
//! Matrix — full executor comparison.
//! 16 funnel policy variants × 14 workload scenarios + all baselines.
//! Reproduce: make bench-compare (or make -C hylic-benchmark _bench-matrix)

#[path = "support/mod.rs"]
mod support;

use criterion::{criterion_group, criterion_main, Criterion};
use std::hint::black_box;
use hylic::exec::funnel;
use support::scenario::{self, Scale, PreparedScenario};
use support::executor_set::{ExecutorSet, FunnelSpecs};
use support::{runners, baselines, bench_cell};

fn bench_matrix(c: &mut Criterion) {
    let nw = support::config::bench_workers();

    funnel::Pool::with(nw, |fpool| {
        let es = ExecutorSet { fpool, nw, funnel: FunnelSpecs::new(nw) };
        let mut group = c.benchmark_group("matrix");

        for def in scenario::all_scenarios(Scale::from_env()) {
            let s = PreparedScenario::from_def(&def, "sm");
            let p = s.as_problem();
            let mut all = runners::all_hylic_runners(&p, &es);
            all.extend(baselines::hand_baselines(&s));
            for r in &all {
                bench_cell(&mut group, r.name, &p.name,
                    |b, _| b.iter(|| black_box((r.run)())),
                );
            }
        }

        group.finish();
    });
}

criterion_group!(benches, bench_matrix);
criterion_main!(benches);
}
Module simulation harness
#![allow(unused)]
fn main() {
//! Module simulation — realistic workload.
//! Dependency graph resolution with simulated file parsing and I/O.
//! Reproduce: make -C hylic-benchmark _bench-modsim

#[path = "support/mod.rs"]
mod support;

use criterion::{criterion_group, criterion_main, Criterion};
use std::hint::black_box;
use hylic::exec::funnel;
use support::executor_set::{ExecutorSet, FunnelSpecs};
use support::{module_sim, runners, bench_cell};

fn bench_modsim(c: &mut Criterion) {
    let nw = support::config::bench_workers();

    funnel::Pool::with(nw, |fpool| {
        let es = ExecutorSet { fpool, nw, funnel: FunnelSpecs::new(nw) };
        let mut group = c.benchmark_group("modsim");

        for spec in module_sim::all_module_scenarios(false) {
            let sim = module_sim::prepare(&spec);
            let p = module_sim::as_problem(&sim);

            let mut all = runners::all_hylic_runners(&p, &es);
            all.extend(module_sim::vanilla_baselines(&sim));

            for r in &all {
                bench_cell(&mut group, r.name, &p.name,
                    |b, _| b.iter(|| black_box((r.run)())),
                );
            }
        }

        group.finish();
    });
}

criterion_group!(benches, bench_modsim);
criterion_main!(benches);
}
Runner matrix construction
#![allow(unused)]
fn main() {
//! Benchmark runners — uniform constructors generic over node type.
//!
//! Every runner is `Runner<'a> = { name, Box<dyn Fn() -> u64> }`.
//! Hylic runners and handrolled baselines share the same type.
//! All hylic runners are generic over N — the same code serves
//! NodeId scenarios and String module-sim scenarios.

use hylic::domain::shared as dom;
use hylic::exec::funnel;
use hylic::exec::funnel::policy::FunnelPolicy;

use super::problem::BenchProblem;
use super::executor_set::ExecutorSet;

// ── Runner type ─────────────────────────────────────

pub struct Runner<'a> {
    pub name: &'static str,
    pub run: Box<dyn Fn() -> u64 + 'a>,
}

// ── Direct executors (generic over N) ───────────────

pub fn rayon<'a, N: Clone + Send + Sync + 'static>(p: &'a BenchProblem<N>) -> Runner<'a> {
    let exec = dom::exec(hylic_benchmark::executor::rayon::Spec);
    Runner { name: "rayon", run: Box::new(move || exec.run(&p.fold, &p.treeish, &p.root)) }
}

pub fn funnel_variant<'a, N: Clone + Send + 'static, P: FunnelPolicy>(
    name: &'static str, p: &'a BenchProblem<N>, es: &'a ExecutorSet, spec: funnel::Spec<P>,
) -> Runner<'a> {
    let exec = dom::exec(spec).attach(es.fpool);
    Runner { name, run: Box::new(move || exec.run(&p.fold, &p.treeish, &p.root)) }
}

// ── Grouped constructors ────────────────────────────

/// All 16 funnel policy variants: 4 queue×accumulate × 4 wake.
pub fn funnel_runners<'a, N: Clone + Send + 'static>(
    p: &'a BenchProblem<N>, es: &'a ExecutorSet,
) -> Vec<Runner<'a>> {
    let s = &es.funnel;
    vec![
        // PerWorker + OnArrival × wake
        funnel_variant("funnel.pw.arrv.push",     p, es, s.pw_arrv),
        funnel_variant("funnel.pw.arrv.batch",    p, es, s.pw_arrv_batch),
        funnel_variant("funnel.pw.arrv.k4",       p, es, s.pw_arrv_k4),
        funnel_variant("funnel.pw.arrv.k2",       p, es, s.pw_arrv_k2),
        // Shared + OnArrival × wake
        funnel_variant("funnel.sh.arrv.push",     p, es, s.sh_arrv),
        funnel_variant("funnel.sh.arrv.batch",    p, es, s.sh_arrv_batch),
        funnel_variant("funnel.sh.arrv.k4",       p, es, s.sh_arrv_k4),
        funnel_variant("funnel.sh.arrv.k2",       p, es, s.sh_arrv_k2),
        // PerWorker + OnFinalize × wake
        funnel_variant("funnel.pw.fin.push",      p, es, s.pw_fin),
        funnel_variant("funnel.pw.fin.batch",     p, es, s.pw_fin_batch),
        funnel_variant("funnel.pw.fin.k4",        p, es, s.pw_fin_k4),
        funnel_variant("funnel.pw.fin.k2",        p, es, s.pw_fin_k2),
        // Shared + OnFinalize × wake
        funnel_variant("funnel.sh.fin.push",      p, es, s.sh_fin),
        funnel_variant("funnel.sh.fin.batch",     p, es, s.sh_fin_batch),
        funnel_variant("funnel.sh.fin.k4",        p, es, s.sh_fin_k4),
        funnel_variant("funnel.sh.fin.k2",        p, es, s.sh_fin_k2),
    ]
}

/// All hylic runners: baselines + full funnel matrix.
pub fn all_hylic_runners<'a, N: Clone + Send + Sync + 'static>(
    p: &'a BenchProblem<N>, es: &'a ExecutorSet,
) -> Vec<Runner<'a>> {
    let mut v = vec![rayon(p)];
    v.extend(funnel_runners(p, es));
    v
}
}
Handrolled baselines
#![allow(unused)]
fn main() {
//! Handrolled baselines — problem-specific, not generic over N.
//!
//! These bypass the hylic fold/treeish abstraction entirely,
//! operating on raw adjacency lists or domain-specific data.
//! They exist to measure framework overhead.

use super::runners::Runner;
use super::problem::BenchProblem;
use super::tree::NodeId;
use super::work::WorkSpec;
use super::scenario::PreparedScenario;

// ── Fused executor (hylic sequential baseline) ──────

pub fn fused<'a, N: Clone + 'static>(p: &'a BenchProblem<N>) -> Runner<'a> {
    use hylic::domain::shared as dom;
    Runner { name: "fused", run: Box::new(|| dom::FUSED.run(&p.fold, &p.treeish, &p.root)) }
}

// ── Handrolled baselines (no hylic, raw recursion) ──

pub fn hand_seq<'a>(s: &'a PreparedScenario) -> Runner<'a> {
    Runner { name: "hand.seq", run: Box::new(|| handrolled_seq(s)) }
}

pub fn hand_rayon<'a>(s: &'a PreparedScenario) -> Runner<'a> {
    Runner { name: "hand.rayon", run: Box::new(|| handrolled_rayon(s)) }
}

pub fn real_seq<'a>(s: &'a PreparedScenario) -> Runner<'a> {
    Runner { name: "real.seq", run: Box::new(|| realworld_seq(s)) }
}

pub fn real_rayon<'a>(s: &'a PreparedScenario) -> Runner<'a> {
    Runner { name: "real.rayon", run: Box::new(|| realworld_rayon(s)) }
}

/// Parallel handrolled baselines for the matrix benchmark.
/// Sequential baselines (hand.seq, real.seq) are in the overhead suite only.
pub fn hand_baselines<'a>(s: &'a PreparedScenario) -> Vec<Runner<'a>> {
    vec![hand_rayon(s), real_rayon(s)]
}

// ── Implementations ─────────────────────────────────

fn handrolled_seq(s: &PreparedScenario) -> u64 {
    fn recurse(children: &[Vec<NodeId>], work: &WorkSpec, node: NodeId) -> u64 {
        work.do_graph();
        let mut heap = work.do_init();
        for &child in &children[node] {
            work.do_accumulate(&mut heap, &recurse(children, work, child));
        }
        work.do_finalize(&heap)
    }
    recurse(&s.children, &s.work, s.root)
}

fn handrolled_rayon(s: &PreparedScenario) -> u64 {
    use rayon::prelude::*;
    fn recurse(children: &[Vec<NodeId>], work: &WorkSpec, node: NodeId) -> u64 {
        work.do_graph();
        let mut heap = work.do_init();
        let ch = &children[node];
        if ch.len() <= 1 {
            for &child in ch {
                work.do_accumulate(&mut heap, &recurse(children, work, child));
            }
        } else {
            let results: Vec<u64> = ch.par_iter()
                .map(|&c| recurse(children, work, c))
                .collect();
            for r in &results { work.do_accumulate(&mut heap, r); }
        }
        work.do_finalize(&heap)
    }
    recurse(&s.children, &s.work, s.root)
}

fn realworld_seq(s: &PreparedScenario) -> u64 {
    use std::collections::HashMap;
    fn resolve(children: &[Vec<NodeId>], work: &WorkSpec, node: NodeId, cache: &mut HashMap<NodeId, u64>) -> u64 {
        if let Some(&v) = cache.get(&node) { return v; }
        work.do_graph();
        let mut heap = work.do_init();
        for &child in &children[node] {
            work.do_accumulate(&mut heap, &resolve(children, work, child, cache));
        }
        let result = work.do_finalize(&heap);
        cache.insert(node, result);
        result
    }
    let mut cache = HashMap::new();
    resolve(&s.children, &s.work, s.root, &mut cache)
}

fn realworld_rayon(s: &PreparedScenario) -> u64 {
    use rayon::prelude::*;
    fn resolve(children: &[Vec<NodeId>], work: &WorkSpec, node: NodeId) -> u64 {
        work.do_graph();
        let mut heap = work.do_init();
        let ch = &children[node];
        if ch.len() <= 1 {
            for &child in ch {
                work.do_accumulate(&mut heap, &resolve(children, work, child));
            }
        } else {
            let results: Vec<u64> = ch.par_iter()
                .map(|&c| resolve(children, work, c))
                .collect();
            for r in &results { work.do_accumulate(&mut heap, r); }
        }
        work.do_finalize(&heap)
    }
    resolve(&s.children, &s.work, s.root)
}
}
Funnel policy specs
#![allow(unused)]
fn main() {
//! ExecutorSet: shared resources for all benchmark runners.

use hylic::exec::funnel;
use hylic::exec::funnel::policy;
use hylic::exec::funnel::wake;

/// All 16 funnel policy variants: 4 queue×accumulate × 4 wake.
pub struct FunnelSpecs {
    // PerWorker + OnFinalize × 4 wake
    pub pw_fin:           funnel::Spec<policy::Default>,
    pub pw_fin_batch:     funnel::Spec<policy::LowOverhead>,
    pub pw_fin_k4:        funnel::Spec<policy::HighThroughput>,
    pub pw_fin_k2:        funnel::Spec<policy::DeepNarrow>,
    // PerWorker + OnArrival × 4 wake
    pub pw_arrv:          funnel::Spec<policy::PerWorkerArrival>,
    pub pw_arrv_batch:    funnel::Spec<policy::Policy<funnel::queue::PerWorker, funnel::accumulate::OnArrival, wake::OncePerBatch>>,
    pub pw_arrv_k4:       funnel::Spec<policy::Policy<funnel::queue::PerWorker, funnel::accumulate::OnArrival, wake::EveryK<4>>>,
    pub pw_arrv_k2:       funnel::Spec<policy::Policy<funnel::queue::PerWorker, funnel::accumulate::OnArrival, wake::EveryK<2>>>,
    // Shared + OnFinalize × 4 wake
    pub sh_fin:           funnel::Spec<policy::SharedDefault>,
    pub sh_fin_batch:     funnel::Spec<policy::Policy<funnel::queue::Shared, funnel::accumulate::OnFinalize, wake::OncePerBatch>>,
    pub sh_fin_k4:        funnel::Spec<policy::Policy<funnel::queue::Shared, funnel::accumulate::OnFinalize, wake::EveryK<4>>>,
    pub sh_fin_k2:        funnel::Spec<policy::Policy<funnel::queue::Shared, funnel::accumulate::OnFinalize, wake::EveryK<2>>>,
    // Shared + OnArrival × 4 wake
    pub sh_arrv:          funnel::Spec<policy::WideLight>,
    pub sh_arrv_batch:    funnel::Spec<policy::StreamingWide>,
    pub sh_arrv_k4:       funnel::Spec<policy::Policy<funnel::queue::Shared, funnel::accumulate::OnArrival, wake::EveryK<4>>>,
    pub sh_arrv_k2:       funnel::Spec<policy::Policy<funnel::queue::Shared, funnel::accumulate::OnArrival, wake::EveryK<2>>>,
}

// ANCHOR: funnel_specs
impl FunnelSpecs {
    pub fn new(nw: usize) -> Self {
        use funnel::wake::once_per_batch::OncePerBatchSpec;
        use funnel::wake::every_k::EveryKSpec;

        FunnelSpecs {
            // PW + Final × wake
            pw_fin:         funnel::Spec::default(nw),
            pw_fin_batch:   funnel::Spec::for_low_overhead(nw),
            pw_fin_k4:      funnel::Spec::for_high_throughput(nw),
            pw_fin_k2:      funnel::Spec::for_deep_narrow(nw),
            // PW + Arrive × wake
            pw_arrv:        funnel::Spec::for_perworker_arrival(nw),
            pw_arrv_batch:  funnel::Spec::for_perworker_arrival(nw).with_wake::<wake::OncePerBatch>(OncePerBatchSpec),
            pw_arrv_k4:     funnel::Spec::for_perworker_arrival(nw).with_wake::<wake::EveryK<4>>(EveryKSpec),
            pw_arrv_k2:     funnel::Spec::for_perworker_arrival(nw).with_wake::<wake::EveryK<2>>(EveryKSpec),
            // SH + Final × wake
            sh_fin:         funnel::Spec::for_shared_default(nw),
            sh_fin_batch:   funnel::Spec::for_shared_default(nw).with_wake::<wake::OncePerBatch>(OncePerBatchSpec),
            sh_fin_k4:      funnel::Spec::for_shared_default(nw).with_wake::<wake::EveryK<4>>(EveryKSpec),
            sh_fin_k2:      funnel::Spec::for_shared_default(nw).with_wake::<wake::EveryK<2>>(EveryKSpec),
            // SH + Arrive × wake
            sh_arrv:        funnel::Spec::for_wide_light(nw),
            sh_arrv_batch:  funnel::Spec::for_streaming_wide(nw),
            sh_arrv_k4:     funnel::Spec::for_wide_light(nw).with_wake::<wake::EveryK<4>>(EveryKSpec),
            sh_arrv_k2:     funnel::Spec::for_wide_light(nw).with_wake::<wake::EveryK<2>>(EveryKSpec),
        }
    }
}
// ANCHOR_END: funnel_specs

/// Shared resources for a benchmark session. Constructed once, passed to all runners.
pub struct ExecutorSet<'a> {
    pub fpool: &'a funnel::Pool<'a>,
    pub nw: usize,
    pub funnel: FunnelSpecs,
}
}

Correctness

Performance numbers are uninformative without correctness. The Funnel executor has a unit and integration suite under hylic/src/exec/variant/funnel/tests/ covering the API, parity with the Fused baseline, and deterministic results across all policy variants. An interleaving stress harness in tests/interleaving.rs and tests/stress.rs exercises the scheduler under aggressive steal patterns. Every benchmark harness asserts that the computed R matches a reference Fused run (PreparedScenario::expected) before timing begins; a policy variant producing a faster-but-incorrect answer would never reach the tables above.