−39%
--json wall time
2.13 s→1.30 s
each round −46% to −38%
Latest run · Sep 26, 2026
Performance is a feature. Architecture is measurable.
What three architecture phases changed in Sprout, measured on llvm-project (185,509 files) against the v0.2.0 release. Speed claims count only when the result held in every one of 5 interleaved rounds.
−39%
2.13 s→1.30 s
each round −46% to −38%
−80%
547 MB→109 MB
each round −84% to −77%
−80%
1,128 MB→231 MB
−27%
88.3 MB→64.5 MB
−71%
4.03→1.17
−27%
157 MB→115 MB
each round −27% to −24%
A faster command, a smaller heap, fewer allocations, a better algorithm and a more correct answer are different claims. Each result on this page carries exactly one of these labels.
Three phases, each merged as its own pull request and measured against the same baseline.
BeforeRecursive JSON materialization: every node marshalled into a temporary buffer, then the whole document re-indented
AfterOne streaming writer that encodes each value once into a reused buffer; compact by default, --pretty opt-in
2.13 s → 1.30 s−39%1,128 MB → 231 MB−80%547 MB → 109 MB−80%BeforeOne heap allocation per node, plus a stored absolute path and a separate name string
AfterOne allocation per directory, names sharing their relative path's bytes, and filesystem paths rebuilt on demand
88.3 MB → 64.5 MB−27%4.03 → 1.17−71%BeforePer-file import maps, forward edges only, package-level Go resolution against the root module
AfterInteger file IDs with forward and reverse adjacency, test relationships, file-level Go edges and every go.mod in the tree
~27.7k → 47,906More edges because nested Go modules now resolve and edges point at the file actually used. The baseline count varied between runs (27,692–27,702); the current count is the same every run.
O(N) scan · 108,827 ns → O(1) lookup · 0.7 nsComplexity improvement, not a measured user-facing speedup. v0.2.0 had no command that asked for dependents, and no current command's runtime is dominated by these lookups.
Every measurement from this run, grouped by kind. Where a per-round range is shown (wall time, peak RSS), the change is the median of the paired per-round changes across all 5 rounds; absolute wall times are medians of steady rounds 2–4. Live-heap and allocation figures are near-deterministic, so their change follows directly from before and after.
| Measurement | Repository | Command | Baseline | Current | Change |
|---|---|---|---|---|---|
| --json wall time | llvm-project | sprout --json | 2.13 s | 1.30 s | −39%each round −46% to −38% |
| --json --pretty wall timeBaseline has no --pretty flag; its --json was the indented form and is byte-identical to the current --json --pretty. | llvm-project | sprout --json --pretty | 2.11 s | 1.20 s | −40%each round −45% to −37% |
| --json wall time | kubernetes | sprout --json | 0.56 s | 0.33 s | −39%each round −50% to −31% |
| --json --pretty wall time | kubernetes | sprout --json --pretty | 0.50 s | 0.30 s | −40%each round −43% to −39% |
| --entry wall time | llvm-project | sprout --entry | 1.11 s | 1.04 s | −6%each round −15% to −1% |
| Measurement | Repository | Command | Baseline | Current | Change |
|---|---|---|---|---|---|
| --json peak RSS | llvm-project | sprout --json | 547 MB | 109 MB | −80%each round −84% to −77% |
| --json --pretty peak RSS | llvm-project | sprout --json --pretty | 622 MB | 110 MB | −83%each round −84% to −74% |
| --json peak RSS | kubernetes | sprout --json | 136 MB | 29.0 MB | −79%each round −82% to −77% |
| Tree peak RSS | llvm-project | sprout | 139 MB | 113 MB | −18%each round −20% to −15% |
| --entry peak RSS | llvm-project | sprout --entry | 157 MB | 115 MB | −27%each round −27% to −24% |
| --ai peak RSS | llvm-project | sprout --ai | 235 MB | 194 MB | −18%each round −21% to −16% |
| Tree live memory | llvm-project | in-process, 201,148 nodes | 88.3 MB | 64.5 MB | −27% |
| Live bytes per node | llvm-project | in-process | 460 B | 336 B | −27% |
| Live heap objects per node | llvm-project | in-process | 4.03 | 1.17 | −71% |
| Tree live memory | kubernetes | in-process, 37,183 nodes | 19.9 MB | 13.2 MB | −34% |
| Measurement | Repository | Command | Baseline | Current | Change |
|---|---|---|---|---|---|
| --json bytes allocatedBaseline allocation varies about 2% between runs (encoding/json buffer growth). | llvm-project | sprout --json | 1,128 MB | 231 MB | −80% |
| --json allocations | llvm-project | sprout --json | 2.52 M | 1.52 M | −40% |
| Allocations to build the tree | llvm-project | in-process | 1.21 M | 0.81 M | −33% |
| Graph build allocations | kubernetes | in-process, no tests | 16.55 M | 16.19 M | −2% |
| Measurement | Repository | Command | Baseline | Current | Change |
|---|---|---|---|---|---|
| Dependents lookupComplexity improvement, not a measured user-facing speedup. v0.2.0 had no command that asked for dependents, and no current command's runtime is dominated by these lookups. | kubernetes | in-memory microbenchmark | O(N) scan · 108,827 ns | O(1) lookup · 0.7 ns | O(N) → O(1) |
| Measurement | Repository | Command | Baseline | Current | Change |
|---|---|---|---|---|---|
| Dependency edges foundMore edges because nested Go modules now resolve and edges point at the file actually used. The baseline count varied between runs (27,692–27,702); the current count is the same every run. | kubernetes | graph, no tests | ~27.7k | 47,906 | Changed |
Some changes were faster in some rounds and slower in others. When the rounds disagree, there is no claim: each range below crosses zero, which is exactly why it isn't counted.
0.82 s → 0.76 s No consistent performance change
−13%0+4%
2.20 s → 2.25 s No consistent performance change
−14%0+12%
294 ms → 280 ms No consistent performance change
−23%0+8%
0.64 s → 0.57 s No consistent performance change
Output differs after Phase 3 (more precise edges), so this compares different work.
−23%0+6%
JSON serialization is no longer the interesting part of the profile. Filesystem traversal and Git work are now what cost the most. From CPU and allocation profiles of the current revision:
% of CPU
Reading directories is also 76% of CPU for the plain tree.
seconds, out of a 2.25 s run
%
Shares come from separate CPU and allocation profiles, so they don't add up.
Every controlled run, newest first. Runs are added to src/data/benchmarks/history.json; the page is rebuilt from that file, and the build fails if a percentage disagrees with its before/after values or per-round range, or if an "improved" claim didn't hold in every round.
Speedup claims were only counted when the direction held across all 5 interleaved rounds.
| Repository | Revision | Files | Role |
|---|---|---|---|
| llvm-project | 9f8d81253 | 185,509 | large heterogeneous repository |
| kubernetes | dfd7b93a | 31,412 | Go-heavy, multi-module |
| sprout | 636d292 | 44 | small single-package Go |
| ky | 0d59458 | 104 | small TypeScript control |
| click | 06b2a67 | 178 | small Python control |
| anyhow | c63b279 | 54 | small Rust control |