# pipe-io v0.4.0 - Polish and benchmarking
**Date:** 2026-05-19
**Compare:** `v0.3.0...v0.4.0`
## Headline
Roadmap milestone `0.4.0` - benchmarks and a measured performance
section in the docs. No public API changes from `0.3.0`. The
release adds `benches/pipeline.rs`, `docs/BENCH.md`, and a
performance summary inside `docs/API.md`.
The numbers are healthy. On a developer laptop (Windows, x86_64,
release with `lto = "thin"`), a single map runs at ~260 M items / s
through the sync driver and each added stage costs roughly 1-2 ns
per item - one vtable dispatch through the boxed stage chain. That
gap to the raw-iterator baseline is the architectural cost of the
type-erased builder, documented in `BENCH.md`. No optimization pass
was needed for this release.
## What changed since `v0.3.0`
### New - `benches/pipeline.rs`
Hand-rolled (no Criterion) throughput bench covering:
- Raw `Iterator::map().filter()` baseline (for context).
- `source -> null sink` (driver overhead floor).
- `source -> map -> sink`.
- `source -> map -> filter -> map -> sink`.
- `source -> filter(false) -> sink` (drop-everything path).
- `source -> batch(100) -> sink`.
- `source -> try_map(ok) -> sink` (happy path).
- `source -> try_map(50% err, Continue)` (error policy cost).
- `threaded driver: source -> map -> sink`.
Each scenario warms up once, then times one timed pass of 200,000
items. Per-item nanoseconds and millions of items per second are
printed to stdout. Reproduce with:
```text
cargo bench --bench pipeline
```
### New - `docs/BENCH.md`
Methodology, hardware, full results table, an interpretation of
each scenario, and a note on the architectural cost. Cross-linked
from `docs/API.md`.
### Changed - `docs/API.md` gets a Performance section
Summary table with headline ns / item and M items / s for the
common shapes. Links out to `BENCH.md` for the full picture.
### Changed - `Cargo.toml`
```toml
[[bench]]
name = "pipeline"
harness = false
required-features = ["std"]
```
## Public surface
Unchanged from `0.3.0`. See [`docs/API.md`](../API.md).
### MSRV
`1.75`, unchanged.
### Runtime dependencies
Zero, unchanged.
## Headline numbers
200,000 items per run. Hardware: developer laptop (Windows 11,
x86_64-pc-windows-msvc, release with `lto = "thin"`).
| raw `Iterator::map().filter()` baseline | ~0 | ~2,200 |
| source -> null sink | 1 | ~500 |
| source -> map -> sink | 3 | ~260 |
| source -> map -> filter -> map -> sink | 5 | ~170 |
| source -> filter(false) -> sink | 2 | ~340 |
| source -> batch(100) -> sink | 7 | ~140 |
| source -> try_map(ok) -> sink | 3 | ~290 |
| source -> try_map(50% err, Continue) | 3 | ~325 |
| threaded driver: source -> map -> sink | 46 | ~21 |
Reading the numbers:
- Each stage edge costs one boxed-dyn vtable call (~1-2 ns per item).
Three stages in a chain adds ~4-5 ns vs the source-only baseline.
- Filtered-out items pay only the filter closure cost; downstream
stages are skipped.
- The batching cost (7 ns / item at batch size 100) is the per-item
`Vec::push` plus the amortized cost of emitting one batch every
100 items.
- `try_map` matches plain `map` in the happy path. With 50% errors
routed through `Continue`, throughput is unchanged - the swallowed
error is one vtable hop.
- The threaded driver pays for one thread spawn + join plus moving
the pipeline closure into the worker. For 200,000 items these
fixed costs amortize to ~40 ns / item. Long-running pipelines or
blocking source pulls amortize better; this driver is not a
speedup for trivially-fast stages.
## Verification
Local matrix (Windows x86_64-pc-windows-msvc, stable 1.95):
- `cargo build` (default features) [OK]
- `cargo build --no-default-features` [OK]
- `cargo build --all-features` [OK]
- `cargo +1.75 build --all-features` (MSRV) [OK]
- `cargo fmt --all -- --check` [OK]
- `cargo clippy --all-targets --no-default-features -- -D warnings` [OK]
- `cargo clippy --all-targets --all-features -- -D warnings` [OK]
- `cargo test --all-features` - **50 tests pass, 0 ignored**
(27 unit + 15 integration + 8 doctest)
- `cargo bench --bench pipeline` - runs to completion with the
headline numbers above
- `RUSTDOCFLAGS="-D warnings -D rustdoc::broken-intra-doc-links" cargo doc --all-features --no-deps` [OK]
- Banned-word and em-dash scan across `src/`, `tests/`, `benches/`,
`docs/`, `README.md`, `CHANGELOG.md`, `REPS.md`: 0 hits
CI matrix continues to run on ubuntu-latest, macos-latest,
windows-latest.
## Migration
### From `v0.3.0`
No source changes required. The public API surface is unchanged.
## Stability commitment
Unchanged. `0.x.y` releases are not API-stable; API freeze begins
at `1.0.0` (see `REPS.md` section 8). The three slices deferred
in `0.3.0` (the `window` module, the unified `Driver` trait, and
the dead-letter sink wiring) remain deferred and may evolve before
they land.
## Release ceremony
```bash
git tag -a v0.4.0 -m "Release v0.4.0 - Polish and benchmarking"
git push origin main
git push origin v0.4.0
cargo publish
GitHub release title: `v0.4.0 - Polish and benchmarking`. Not
tagged as pre-release.
## Acknowledgements
`pipe-io 0.4.0` closes out the third roadmap milestone (Polish and
benchmarking). The implementation is steady-state: no public API
shifts, no dependency adds, MSRV holds at 1.75, and the crate ships
zero runtime dependencies with `#![forbid(unsafe_code)]`.
The next roadmap stop is the `0.9.x` pre-1.0 stabilization line
(property tests, fuzz targets, `cargo-semver-checks` in CI, final
documentation audit, migration guide). The three locked-but-not-
shipped surfaces (window, Driver trait, dead-letter wiring) sit in
between.
---
**Full Changelog:** https://github.com/jamesgober/pipe-io/compare/v0.3.0...v0.4.0