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

Performance Guide

Stillwater aims for low-overhead validation, effects, and error context. Effect composition uses concrete types without combinator boxing by default. This guide documents how those properties are measured rather than treating “zero cost” as a blanket promise.

Running benchmarks locally

# All benchmark suites (validation, effects, context, parallel)
cargo bench --features async

# Individual suites
cargo bench --bench validation
cargo bench --bench effects --features async
cargo bench --bench context
cargo bench --bench parallel --features async

Criterion writes HTML reports under target/criterion/. Open any report/index.html in a browser for detailed plots.

For a quick smoke run (pass --sample-size only to criterion benches, not the library test harness):

cargo bench --features async \
  --bench validation --bench effects --bench context --bench parallel \
  -- --sample-size 10

Benchmark categories

SuiteFileCompares
Validationbenches/validation.rsValidation::all_vec vs manual Result accumulation
Effectsbenches/effects.rsCombinator chain (map + and_then) vs hand-written async
Contextbenches/context.rsContextError layering vs equivalent manual struct
Parallelbenches/parallel.rspar_all / par2 vs sequential run

Each suite pairs Stillwater APIs with hand-written equivalents so overhead is measurable, not assumed.

Expected characteristics

On typical release builds:

  • Validation — Within a few percent of manual accumulation for success paths; error accumulation adds work proportional to error count in both paths.
  • Effects — Combinator chains should match manual async code when effects use concrete types (no .boxed() in the hot path).
  • ContextContextError should be within a few percent of a manual Vec<String> trail; both allocate context strings.
  • Parallelpar_all on cheap pure effects measures scheduling overhead; use I/O-heavy effects locally to validate real speedup.

The project success metric is within 5% of hand-written equivalent code on CPU-bound paths. Absolute numbers vary by machine; compare relative ratios and track trends over time.

CI integration

The Benchmarks workflow:

  • On pull requests: runs benchmarks with reduced samples as a compile-and-run smoke test.
  • On push to master: runs full benchmarks and stores results via github-action-benchmark for historical comparison.

Regressions are detected by comparing new results to the stored baseline on the default branch.

Size smoke tests

Checks in src/effect/combinators/size_tests.rs measure a few concrete struct sizes. They can catch some size growth; a small struct can still contain pointers to heap allocations. They do not prove inline storage, allocation counts, or runtime overhead. Use allocation instrumentation for allocation claims and runtime benchmarks for hot paths.

Practices for low-overhead usage:

  1. Prefer concrete effect types; use .boxed() where type erasure is needed. Borrowed async construction boxes its future, and helpers such as IO, traversal, and retry return boxed effects internally. Account for those costs too.
  2. Use Validation::all_vec / tuple validate_all for accumulation instead of fail-fast ? when you need every error.
  3. Add ContextError at boundaries, not inside tight inner loops.

Updating this document

After significant API changes, re-run cargo bench --features async on release mode and note any ratio shifts in PR descriptions. Do not commit machine-specific timings here; rely on CI history for regression tracking.