Benchmarks¶
This page documents how the 8.0.0 Rust CLI compares to the 7.0.1 Python CLI. Both versions share the same Rust analysis engine; the difference is the CLI pipeline: argument parsing, config resolution, path walking, and output rendering now run in Rust, and the Python interpreter no longer does any analysis work.
Methodology¶
- New CLI (pre-release): built from the release branch with
uv run maturin develop --release, invoked as.venv/bin/complexipy. It reports version 7.0.1 because the version bump happens at release time; the exact commit is recorded in the environment block below. - Baseline CLI:
complexipy==7.0.1installed from PyPI withuv addinto an isolated project, invoked as its venv'sbin/complexipy. - Invocation parity: both CLIs run as bare console scripts with
byte-identical flags. The
uv runwrapper is excluded from timing so neither side pays uv process overhead. - Corpus: real open-source repos, shallow-cloned at pinned commits, plus a single-file probe that isolates interpreter and CLI startup from tree analysis.
- Metrics: wall time via hyperfine (warmup 3, 5 runs; probe warmup 5,
20 runs) with stdout discarded; peak RSS via
/usr/bin/time -l, 3 runs, maximum reported. - Parity gate: before timing, both CLIs export JSON for every corpus repo. The exports are byte-identical and the exit codes match, so the comparison measures the same work on both sides.
The benchmark is repeatable:
Environment¶
- Machine: MacBookPro18,1 (arm64)
- OS: macOS 26.5.1
- CPU: Apple M1 Pro
- RAM: 16 GB
- New CLI: complexipy 7.0.1 (built from 1699696, python 3.12.12)
- Baseline CLI: 7.0.1 (PyPI, python 3.12.12)
- uv: uv 0.10.3 (c75a0c625 2026-02-16)
- hyperfine: hyperfine 1.20.0
- Runs: hyperfine warmup=3 runs=5 (probe warmup=5 runs=20); RSS via /usr/bin/time -l, 3 runs, maximum
- Modes: default, --quiet, --failed, and render (full output written to a file)
- Scaling fixture: 285 base functions, sizes 1x/2x/4x, 5 runs each
- Date: 2026-08-31
Corpus (shallow clones at pinned commits):
- requests @ 5460f467b02e49471c0fd6cfc9ca0adab6351f98 (37 files)
- flask @ d318b683471101618febed18996405ad26462110 (83 files)
- django @ 0b40210e4808937a7c0922e8b7502bff4752faa3 (2929 files)
- single-file probe: requests/src/requests/init.py @ 5460f467b02e49471c0fd6cfc9ca0adab6351f98
Wall time¶
| Workload | 8.0.0 Rust CLI | 7.0.1 Python CLI | Speedup |
|---|---|---|---|
| requests (37 files) [default] | 0.067 s ± 0.001 s | 0.373 s ± 0.002 s | 5.56x |
| requests (37 files) [quiet] | 0.064 s ± 0.002 s | 0.331 s ± 0.001 s | 5.16x |
| requests (37 files) [failed] | 0.065 s ± 0.001 s | 0.343 s ± 0.002 s | 5.24x |
| requests (37 files) [render] | 0.067 s ± 0.002 s | 0.376 s ± 0.005 s | 5.62x |
| flask (83 files) [default] | 0.074 s ± 0.001 s | 0.474 s ± 0.002 s | 6.42x |
| flask (83 files) [quiet] | 0.070 s ± 0.003 s | 0.406 s ± 0.002 s | 5.76x |
| flask (83 files) [failed] | 0.073 s ± 0.001 s | 0.424 s ± 0.004 s | 5.80x |
| flask (83 files) [render] | 0.076 s ± 0.002 s | 0.477 s ± 0.002 s | 6.32x |
| django (2929 files) [default] | 0.445 s ± 0.002 s | 8.069 s ± 0.022 s | 18.12x |
| django (2929 files) [quiet] | 0.272 s ± 0.003 s | 6.486 s ± 0.011 s | 23.82x |
| django (2929 files) [failed] | 0.440 s ± 0.003 s | 6.920 s ± 0.011 s | 15.71x |
| django (2929 files) [render] | 0.452 s ± 0.004 s | 8.115 s ± 0.026 s | 17.95x |
| single-file probe [default] | 57.5 ms ± 1.0 ms | 231.4 ms ± 1.9 ms | 4.02x |
| single-file probe [quiet] | 57.9 ms ± 1.3 ms | 228.2 ms ± 1.1 ms | 3.94x |
| single-file probe [failed] | 57.6 ms ± 1.1 ms | 234.8 ms ± 6.6 ms | 4.08x |
| single-file probe [render] | 59.9 ms ± 4.4 ms | 234.0 ms ± 4.3 ms | 3.91x |
Scaling (synthetic fixture)¶
| Size | Mean | Ratio | --quiet mean | Ratio | Peak RSS |
|---|---|---|---|---|---|
| 1x (3995 lines) | 62.0 ms | - | 64.7 ms | - | 69 MB |
| 2x (7990 lines) | 68.7 ms | 1.11 | 67.7 ms | 1.05 | 73 MB |
| 4x (15980 lines) | 82.0 ms | 1.19 | 81.6 ms | 1.21 | 82 MB |
The fixture is generated deterministically by benchmarks/generate_scaling_fixture.py (285 base functions, sizes 1x/2x/4x, kept outside the repo under /Users/rhafid/.cache/complexipy-benchmarks/scaling; never committed). Ratios near 2 mean linear scoring; ratios near 4 mean quadratic behavior.
Peak RSS¶
| Workload | 8.0.0 Rust CLI | 7.0.1 Python CLI |
|---|---|---|
| requests (37 files) [default] | 70 MB | 106 MB |
| requests (37 files) [quiet] | 69 MB | 105 MB |
| requests (37 files) [failed] | 70 MB | 106 MB |
| requests (37 files) [render] | 69 MB | 107 MB |
| flask (83 files) [default] | 70 MB | 106 MB |
| flask (83 files) [quiet] | 70 MB | 105 MB |
| flask (83 files) [failed] | 70 MB | 106 MB |
| flask (83 files) [render] | 70 MB | 106 MB |
| django (2929 files) [default] | 106 MB | 120 MB |
| django (2929 files) [quiet] | 107 MB | 119 MB |
| django (2929 files) [failed] | 108 MB | 120 MB |
| django (2929 files) [render] | 107 MB | 120 MB |
| single-file probe [default] | 65 MB | 104 MB |
| single-file probe [quiet] | 65 MB | 103 MB |
| single-file probe [failed] | 65 MB | 104 MB |
| single-file probe [render] | 65 MB | 104 MB |
Notes: stdout is discarded during timing except the [render] mode, which writes the full output to a file; both CLIs run as bare console scripts with identical flags. The django workload exits 1 on both CLIs because its own test fixture (tests/test_runner_apps/tagged/tests_syntax_error.py) is intentionally unparseable.
What the numbers say¶
- Startup-dominated workloads win the most. The single-file probe is about 4x faster, which is the Python interpreter + typer/rich import cost removed from the hot path.
- The scaling table at the bottom of the results measures the Rust engine on a synthetic fixture at 1x, 2x, and 4x sizes. Ratios near 2 mean linear scoring; ratios near 4 mean quadratic behavior. The recorded ratios sit near 1.1-1.2.
- Small and medium trees (requests, flask) are roughly 5-6.4x faster.
- Large trees (django, ~2900 files) are roughly 16-24x faster: the Rust engine analyzes the tree in parallel across all cores, where the Python CLI processed it on one.
- Output rendering is cheaper too. Rendering the full results table on django costs ~0.2 s on the Rust CLI against ~1.6 s on the Python CLI (the default minus --quiet delta), so the wall-clock gap widens on large trees when results are actually printed - the [render] rows measure that path end to end.
- Peak memory is lower across the board, by roughly 15-40 MB, because the Python interpreter and its display libraries are gone from the process.