Benchmarks: how fast alint is
About 118 ms for a 10,000-file repository and 8.83 s for a million files, with an 85-rule workspace config on a 4-core laptop. Each release that changes the linter is timed on that machine after it's tagged, on 4 check scenarios and a dedicated auto-fix pass.
By repository size
Two of the scenarios on synthetic trees from 1,000 to 1,000,000 files: S4, a workspace bundle with 85 rules, and S1, 16 layout and file-name rules. With --changed, alint checks only new files and files with unstaged changes, a tenth of the tree here.
| Files | S4, workspace bundle | S1, layout and names | ||
|---|---|---|---|---|
| Whole tree | --changed | Whole tree | --changed | |
| 1,000 | 54.8 ms | 57.2 ms | 10.2 ms | 13.9 ms |
| 10,000 | 118 ms | 123 ms | 44.1 ms | 52.0 ms |
| 100,000 | 819 ms | 847 ms | 386 ms | 450 ms |
| 1,000,000 | 8.83 s | 8.81 s | 4.24 s | 4.90 s |
| Files | Whole tree | --changed |
|---|---|---|
| 1,000 | 54.8 ms | 57.2 ms |
| 10,000 | 118 ms | 123 ms |
| 100,000 | 819 ms | 847 ms |
| 1,000,000 | 8.83 s | 8.81 s |
| Files | Whole tree | --changed |
|---|---|---|
| 1,000 | 10.2 ms | 13.9 ms |
| 10,000 | 44.1 ms | 52.0 ms |
| 100,000 | 386 ms | 450 ms |
| 1,000,000 | 4.24 s | 4.90 s |
Layout rules cost less than asking git what changed, so S1 is faster on the whole tree at every size. S4 reads file contents across the workspace, and many of its rules look across the whole tree, which --changed still checks in full. Since v0.21.0 the generated sources are clean, so a whole-tree run no longer reports a finding for every file, and from 100,000 files up --changed takes about as long as the whole tree. The --changed timings at 1,000 and 10,000 files are mostly the cost of waking the CPU's cores for a small job, so read them as rough.
Auto-fix
A fifth scenario, new in v0.17 and the only one timed in fix mode: alint fix --unsafe-fixes --dry-run runs every fixable rule's compute-and-compose path, matching each rule and deriving and composing its edits, without writing the files.
| Files | Auto-fix (dry run) |
|---|---|
| 1,000 | 40.8 ms |
| 10,000 | 352 ms |
| 100,000 | 3.71 s |
| 1,000,000 | 37.9 s |
A real tree: in May 2026, the 72 rules that didn't call other tools in the nixpkgs reference config, as it was then, checked a 52,354-file checkout in 332 ms (alint 0.9.17, 10 logical cores). The run is written up in the nixpkgs config's README.
Against scripts and other tools
On Kubernetes, some of alint's rules stand in for hack/verify-*.sh scripts. In May 2026, four of those scripts were timed against one alint pass on the same clone.
verify-file-sizes.sh 4.05 s, verify-boilerplate.sh 1.60 s, verify-pkg-names.sh 439 ms and verify-staging-meta-files.sh 277 ms.
In April 2026, with alint 0.5.7, shell baselines and Repolinter were timed on one 100,000-file tree. On file names, eight find and grep pipelines took about four times as long as alint. On existence and content checks, a shell script using ripgrep was about as fast as alint, and Repolinter took about 14 times as long.
Ryzen 9 3900X, hyperfine with 3 warm-up runs and 10 measured runs, April 2026. These runs predate the engine work in v0.9 and haven't been repeated; the raw results are in the alint repository. The same run timed ls-lint 2.2.3, and that timing was wrong: the harness started ls-lint from a directory without its config, so it stopped before checking anything. ls-lint hasn't been timed again, and MegaLinter, which runs other linters in containers, hasn't been timed at all. Compare covers what each tool checks.
Release by release
Every benchmarked release records full-check timings for S1 to S4 on the million-file tree (S3 from v0.12.0 on). Starting with v0.17.0, it also records SFIX, the dry-run auto-fix pass. Within one workload revision, each scenario ended close to where it started: S1 +2%, S2 0%, S3 +3%, S4 +3% and SFIX +7% from v0.21.0 to v0.22.0. No release in between moved more than 10%. SFIX is a release snapshot: when its fixable-rule set changes, its timing changes with the workload as well as the engine. The releases before this machine became the benchmark host, in July 2026, were timed on it afterwards with the same harness.
| Version | Released | S1 | S2 | S3 | S4 | SFIX | Note |
|---|---|---|---|---|---|---|---|
| v0.22.0 | 2026-11-07 | 4.24 s | 5.29 s | 8 min 10 s | 8.83 s | 37.9 s | |
| v0.21.2 | 2026-11-06 | 4.22 s | 5.36 s | 7 min 55 s | 8.42 s | 36.7 s | |
| v0.21.1 | 2026-11-05 | 4.05 s | 5.20 s | 7 min 58 s | 8.35 s | 37.1 s | |
| v0.21.0 | 2026-11-01 | 4.14 s | 5.30 s | 7 min 56 s | 8.61 s | 35.3 s | workload reset in every scenario: clean generated sources |
| v0.21.0: workload reset in every scenario: clean generated sources | |||||||
| v0.20.1 | 2026-10-23 | 4.08 s | 18.2 s | 8 min 1 s | 15.6 s | 54.4 s | |
| v0.20.0 | 2026-10-21 | 4.22 s | 19.0 s | 7 min 55 s | 16.3 s | 54.2 s | S4 workload reset: 82 of 85 rules apply |
| v0.20.0: S4 workload reset: 82 of 85 rules apply | |||||||
| v0.19.0 | 2026-10-18 | 4.11 s | 18.8 s | 7 min 56 s | 18.7 s | 53.9 s | S4 workload reset: 50 to 85 rules |
| v0.19.0: S4 workload reset: 50 to 85 rules | |||||||
| v0.18.4 | 2026-10-15 | 4.01 s | 18.4 s | 7 min 55 s | 16.2 s | 52.7 s | |
| v0.18.2 | 2026-10-13 | 4.02 s | 18.4 s | 7 min 55 s | 16.3 s | 52.9 s | |
| v0.18.1 | 2026-10-12 | 4.12 s | 18.1 s | 7 min 55 s | 16.1 s | 52.0 s | |
| v0.18.0 | 2026-10-11 | 4.03 s | 19.5 s | 7 min 55 s | 16.6 s | 58.7 s | |
| v0.17.0 | 2026-10-01 | 4.07 s | 18.8 s | 7 min 55 s | 16.6 s | 57.3 s | |
| v0.16.0 | 2026-09-02 | 4.05 s | 18.3 s | 7 min 55 s | 16.5 s | not available | |
| v0.15.0 | 2026-08-16 | 4.18 s | 18.5 s | 7 min 54 s | 16.5 s | not available | |
| v0.14.2 | 2026-08-06 | 4.13 s | 19.1 s | 7 min 54 s | 16.3 s | not available | |
| v0.14.1 | 2026-07-24 | 4.22 s | 21.4 s | 7 min 54 s | 16.4 s | not available | |
| v0.14.0 | 2026-07-18 | 4.14 s | 23.0 s | 7 min 57 s | 18.2 s | not available | slower file reads, fixed in v0.14.1 |
| v0.14.0: slower file reads, fixed in v0.14.1 | |||||||
| v0.13.0 | 2026-06-17 | 4.14 s | 25.0 s | 8 min 7 s | 19.3 s | not available | |
| v0.12.0 | 2026-06-07 | 4.61 s | 25.4 s | 8 min 9 s | 22.0 s | not available | |
| v0.11.0 | 2026-05-28 | 4.27 s | 26.5 s | not run | 23.4 s | not available | |
| v0.10.2 | 2026-05-21 | 4.07 s | 17.9 s | not run | 15.8 s | not available | |
| v0.10.1 | 2026-05-20 | 4.03 s | 17.6 s | not run | 16.0 s | not available | |
| v0.10.0 | 2026-05-20 | 4.06 s | 17.7 s | not run | 16.0 s | not available | |
SFIX is not available before v0.17.0, when alint fix was introduced. Releases without a retained macro-benchmark record, including v0.16.1 and v0.18.3, have no row. The other sizes and scenarios for each release are in HISTORY.md.
Before v0.17, this series used 14 check scenarios; v0.17 consolidated those to S1 through S4 and added SFIX. The earlier releases in the table were re-measured for S1 through S4 with the new harness; their binaries predate alint fix, so SFIX cannot be run for them. The 14-scenario numbers are frozen in the legacy history.
S4's workload is revisioned separately: v0.19.0 expanded it from 50 to 85 resolved rules by adding the versioning and toolchain-pins bundles. Its v0.19.0 timing is the revision-2 baseline, not a like-for-like regression against earlier S4 rows. Revision 2's tree had none of the version and toolchain files those bundles look for, so their rules never applied. v0.20.0's revision 3 commits those files, and 82 of the 85 rules now apply. Its S4 timing is the revision-3 baseline. v0.20.0 also made exact-path facts cheaper, so the change from v0.19.0 mixes workload and engine.
v0.21.0 moved every scenario to a new workload revision: S1 to S3 and SFIX to revision 2, S4 to revision 4. The generated Rust, JavaScript and Python sources now end with a newline, and none of their lines starts or ends with a space. Before, S2's final-newline rule reported a finding for every generated source and its whitespace rules tens of thousands more, about 86,000 findings at 100,000 files, so S2 timed findings as much as linting. Most of the drops at a million files, S2's from 18.2 s to 5.30 s and S4's from 15.6 s to 8.61 s, come from the cleaner tree. On the same clean tree, v0.21.0's code ran S2 8.6% faster than v0.20.1, because the Rust import check now skips a file without use, and S4 about 1% faster, in measurements of pre-release builds rather than the recorded runs. SFIX's drop, from 54.4 s to 35.3 s, was not measured on one tree, so it is not split between the two. The v0.21.0 row is the new baseline for every scenario.
Before this machine, the benchmarks ran on a Ryzen 9 3900X desktop, from v0.5.6 to v0.13.0. That series recorded the two engine fixes that made the million-file tree fast: S3 went from 731.9 s in v0.9.4 to 12.59 s in v0.9.5, and S7 from 614.4 s in v0.9.7 to 15.41 s in v0.9.8. The whole series is in HISTORY.md as it stood before the move to this machine; its numbers don't compare with this machine's.
The scenarios
Each scenario is one config file in the alint repository, run against a generated Cargo-workspace tree. S4 uses a polyglot tree with Rust, Node and Python packages, made into a git repository; S3 builds file graphs across it; SFIX is the one scenario timed in fix mode.
| Scenario | What it runs | Rules |
|---|---|---|
| S1 | Layout and path: file names, globs and file metadata (existence, size), with little or no file read. The ls-lint and shell-pipeline comparison | 16 |
| S2 | Per-file content: content rules over every .rs file, read once and matched by the whole set, plus a structured XML query | 20 |
| S3 | Cross-file, relational and graph: pair, unique_by, the for_each_* family, three file_graph passes and a single-shot command. The heaviest scenario | 16 |
| S4 | Workspace bundle: extends the oss-baseline, rust, node, python, monorepo, cargo-workspace, versioning and toolchain-pins rulesets over a polyglot git tree. The realistic release anchor | 85 |
| SFIX | Auto-fix: every fixable rule's compute and compose path under fix --unsafe-fixes --dry-run. New in v0.17, and the one scenario timed in fix mode |
S3 is much slower than the rest: 47.9 s on 100,000 files and 8 min 10 s on 1,000,000 in v0.22.0, because it builds a graph over the whole tree on every run. The scenario catalog says which part of the engine each exercises; the config files have the detail.
How the numbers are made
- hyperfine times the
alintbinary from outside the process, so startup, file-system calls and output all count, the way you'd run it. - The trees come from a fixed seed (
0xA11E47) and are the same on every run: 1,001, 10,001, 100,001 and 1,000,001 files. With 4 check scenarios at 4 sizes and 2 modes, plus the auto-fix scenario at 4 sizes, that's 36 timings per release. - The alint repository also has criterion micro-benchmarks that time the CPU-bound parts separately: glob and regex matching, the rule engine, the walker, the output formats.
The methodology explains why the tables here use means while the release check compares minimum times.
Catching regressions
Pull requests that change the Rust code get an instruction count for two engine benchmarks, measured under Valgrind with gungraun and compared with the merge base. The count annotates the run and doesn't fail it, and pull requests from forks don't get one. After a release is tagged, the bench host runs the full set, and those are the numbers on this page. Before they're recorded, xtask bench-gate compares them with the previous release's and fails if a like-for-like cell of 10,000 files or more got over 15% slower. Each scenario carries a semantic workload revision; the gate keeps the new run's quality check but does not compare timings across a revision reset. It flags a slow release; it can't stop one that's already tagged.
The two catch different things. In v0.14.0, capping how much of a file alint reads made every read grow its buffer and call read() again. Instruction counts barely moved, since a system call is few instructions however long the kernel takes. The benchmark gate failed on S2 (15.1% slower at 100,000 files). It was first put down to the benchmark setup; the slowdown was real, 12.5 to 15.1% on S2, and v0.14.1 fixed it.
The investigation has the numbers, including the step that first blamed the benchmark setup.
Run them yourself
From a checkout of the alint repository:
cargo run -p xtask --release -- bench-scaleThat runs S1 to S4 and the auto-fix scenario on trees of 1,000 to 100,000 files. --include-1m adds the million-file tree (several GB, about 10 minutes a run), and --tools all adds the shell baselines, ls-lint and Repolinter where they're installed. Its ls-lint timing has the bug described above until the harness is fixed. Compare versions on the same machine; your absolute times will differ from the bench host's.
RUNNING.md lists every flag.