Benchmarks

How fast is FerrFlow?

Side-by-side comparison against the JavaScript ecosystem release tools, measured with hyperfine on the same hardware, the same commits, and the same fixtures. Numbers refresh on every FerrFlow release.

Last update: July 23, 2026 · ferrflow v5.47.0 · binary size 8.0 MB

ferrflow changesets commit-and-tag-version semantic-release standard-version

Median of 30 hyperfine runs · linear scale · cold cache · FerrFlow pinned to --jobs 1 · lower is better. The pale segment at the foot of each bar is that tool's startup — process spawn, runtime boot, module loading — measured against a one-package one-commit floor fixture; the coloured segment above it is the work. The total is what you wait for; the split is where it goes.

Median latency, release --dry-run
Tool1 × 10010 × 10050 × 500100 × 1k50 × 5k200 × 10k50 × 300 · graph
ferrflow7 ms9 ms10 ms16 ms31 ms64 ms20 ms
changesets762 ms842 ms691 ms880 ms684 ms770 ms769 ms
commit-and-tag-version490 ms500 ms405 ms589 ms716 ms1.08 s487 ms
semantic-release1.58 s1.61 s1.27 s1.79 s1.91 s2.48 s1.45 s
standard-version560 ms567 ms468 ms713 ms881 ms1.32 s561 ms

How we measure

  • Hyperfine with 3 warmup runs and 30 timed runs per (fixture × tool × command) cell. Median latency reported.
  • Cold cache. FerrFlow memoises its analysis under .git/ferrflow-cache. That cache is dropped before every timed run, so the comparison measures the work itself — none of the other tools has an equivalent to hide behind. The warm numbers are reported separately below.
  • Peak RSS via /usr/bin/time -v on a single subsequent run.
  • Fixtures are generated from FerrLabs/Fixtures with deterministic conventional-commit history (no flaky inputs). The commit count in each fixture label is the number of commits since the last release tag — the exact window FerrFlow walks to compute a bump, not the repository's total history.
  • All commands are dry-run release planning — every tool computes the next version and release notes without writing anything: ferrflow release --dry-run, semantic-release --dry-run --no-ci, standard-version --dry-run, commit-and-tag-version --dry-run, etc. The same operation for every tool, so we're comparing the planning cost, not the IO cost of actually mutating files.
  • Single-threaded comparison. FerrFlow runs with --jobs 1 in the chart above, so it's measured like the single-threaded JS tools. Its real-world parallel numbers are reported separately.
  • Two cells are not like-for-like, and we'd rather say so than quietly win. On 200 × 10k, changesets status is faster than FerrFlow — but changesets plans releases from .changeset/*.md files you write by hand, and never from commits. Six of our seven fixtures give it none, so it reports no packages to be bumped and stops. Its time tracks package count, not history: it barely moves across a 100× range of commits, because it isn't reading them. That cuts the other way too — most of every row where we beat it is npx and Node startup, not release planning. On 50 × 300 · graph, FerrFlow is slower than commit-and-tag-version because it's the only tool doing the job — resolving a 50-package dependency cascade and recovering missed releases across the history. The others emit one bump and have no notion of a dependency graph. We're fixing the fixtures (Fixtures#141); until then, read those two cells as a measurement gap, not a verdict.
  • Reproducible — the full pipeline is in FerrLabs/Benchmarks; results land as artifacts on every main push and in each release notes.

Parallel mode

The comparison pins FerrFlow to a single thread (--jobs 1) so it's apples-to-apples with the single-threaded JS tools. In real CI it uses every core — here's the same release --dry-run on all 4 cores of the benchmark runner.

Fixture1 thread4 coresSpeedup
Single package · 100 commits7 ms7 ms0.9×
Monorepo · 10 packages × 100 commits9 ms9 ms1.0×
Monorepo · 50 packages × 500 commits10 ms11 ms0.9×
Monorepo · 100 packages × 1k commits16 ms19 ms0.8×
Monorepo · 50 packages × 5k commits31 ms35 ms0.9×
Monorepo · 200 packages × 10k commits64 ms65 ms1.0×
Monorepo · dependency graph · 50 packages × 300 commits20 ms16 ms1.2×

Warm cache

Every number above is measured cold. In practice a repository rarely moves between two ferrflow check calls, so FerrFlow keeps the result of its last analysis in .git/ferrflow-cache and reuses it while HEAD, the tags and the config are unchanged. This is what a second call costs on an unchanged repo — it's a real property of the tool, but it isn't a like-for-like comparison, so it stays out of the chart.

FixtureColdCachedSpeedup
Single package · 100 commits7 ms3 ms2.6×
Monorepo · 10 packages × 100 commits9 ms3 ms2.8×
Monorepo · 50 packages × 500 commits11 ms3 ms3.7×
Monorepo · 100 packages × 1k commits18 ms4 ms4.5×
Monorepo · 50 packages × 5k commits36 ms3 ms12.3×
Monorepo · 200 packages × 10k commits65 ms3 ms19.0×
Monorepo · dependency graph · 50 packages × 300 commits18 ms4 ms4.5×

Install footprint

Disk size after install. release-please appears here only — its runtime can't be benched offline (it needs the GitHub API to resolve the latest release), so footprint is the fair comparison we can publish.

Toolnpm installNative binary
ferrflow8.0 MB8.0 MB
changesets26.0 MB
commit-and-tag-version9.0 MB
release-please95.0 MB
semantic-release69.0 MB
standard-version21.0 MB

Why FerrFlow ships smaller numbers

  • Native binary. No Node.js boot, no npm install, no plugin discovery. A ferrflow check on a 50-package monorepo finishes before node --version would have warmed up.
  • gitoxide in-process. Tag enumeration and commit walking happen via gitoxide in the same process. Competitors shell out to git per call; process-spawn overhead dominates on dense histories.
  • Single tool, not a plugin tree. semantic-release pulls a chain of analyzers, notes generators, npm publishers and changelog writers — each with its own require() cost. FerrFlow's pipeline is one Rust crate.

Numbers updating every release. See the latest release notes for the full table including baseline delta.