Skip to content

perf: do not reallocate rule context struct per-rule-per-file#742

Merged
graphite-app[bot] merged 1 commit intomainfrom
02-24-perf_do_not_reallocate_rule_context_struct_per-rule-per-file
Feb 26, 2026
Merged

perf: do not reallocate rule context struct per-rule-per-file#742
graphite-app[bot] merged 1 commit intomainfrom
02-24-perf_do_not_reallocate_rule_context_struct_per-rule-per-file

Conversation

@camchenry
Copy link
Copy Markdown
Member

@camchenry camchenry commented Feb 25, 2026

~30% faster, ~48% less memory used, and ~37% fewer allocations on our simple benchmark. IMPORTANT: this does not mean that oxlint type-aware linting will run this much faster. When I ran this with oxlint, it was not noticeably faster, it was maybe 1% better. But it was consistently faster, so I think it's worth merging. The intuition for the small real-world speedup is that the bottleneck is in the program creation and reading files from the filesystem, not the linting part itself.

The main idea is that we are currently re-allocating the rule context for every single file and every single rule. In most cases, though the behavior is exactly the same, we are just changing what file are rule we are linting with.

I refactored the diagnostic reporting code to be such that it never reallocates the diagnostic reporting functions: instead, we update the rule name and file and mutate the diagnostic reported with the current state after we've received it. This means we don't need to allocate this struct over and over again, which should reduce pressure on the GC quite a bit (this does show up in the benchmark too).

Before:

goos: darwin
goarch: arm64
pkg: github.com/typescript-eslint/tsgolint/cmd/tsgolint
cpu: Apple M1
BenchmarkAllRulesHeadless-8               	    4495	   2604683 ns/op	 7640387 B/op	   83526 allocs/op
BenchmarkAllRulesHeadless-8               	    4442	   2599917 ns/op	 7675330 B/op	   83833 allocs/op
BenchmarkAllRulesHeadless-8               	    4574	   2590637 ns/op	 7669025 B/op	   83771 allocs/op
BenchmarkAllRulesHeadless-8               	    4442	   2686293 ns/op	 7647700 B/op	   83587 allocs/op
BenchmarkAllRulesHeadless-8               	    4291	   2728212 ns/op	 7654198 B/op	   83647 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2041	   5863713 ns/op	 7629003 B/op	   83638 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2029	   5873142 ns/op	 7622800 B/op	   83581 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2034	   5863612 ns/op	 7614713 B/op	   83508 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2018	   5881180 ns/op	 7630698 B/op	   83649 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2038	   5890125 ns/op	 7635571 B/op	   83689 allocs/op

After:

goos: darwin
goarch: arm64
pkg: github.com/typescript-eslint/tsgolint/cmd/tsgolint
cpu: Apple M1
BenchmarkAllRulesHeadless-8               	    6270	   1827035 ns/op	 4372362 B/op	   52719 allocs/op
BenchmarkAllRulesHeadless-8               	    6024	   1794715 ns/op	 4372635 B/op	   52719 allocs/op
BenchmarkAllRulesHeadless-8               	    6276	   1795347 ns/op	 4354735 B/op	   52559 allocs/op
BenchmarkAllRulesHeadless-8               	    6327	   1837421 ns/op	 4389465 B/op	   52866 allocs/op
BenchmarkAllRulesHeadless-8               	    6244	   1863864 ns/op	 4375612 B/op	   52741 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2756	   4325317 ns/op	 3948056 B/op	   52395 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2791	   4327110 ns/op	 3953848 B/op	   52463 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2770	   4336311 ns/op	 3924766 B/op	   52215 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2758	   4342168 ns/op	 3948428 B/op	   52401 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2726	   4339023 ns/op	 3954526 B/op	   52460 allocs/op

Copy link
Copy Markdown
Member Author

camchenry commented Feb 25, 2026


How to use the Graphite Merge Queue

Add the label 0-merge to this PR to add it to the merge queue.

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@graphite-app graphite-app Bot changed the base branch from 02-24-chore_add_a_sample_benchmark to graphite-base/742 February 25, 2026 03:36
@graphite-app graphite-app Bot force-pushed the 02-24-perf_do_not_reallocate_rule_context_struct_per-rule-per-file branch from f009b39 to 08523d8 Compare February 25, 2026 03:44
@graphite-app graphite-app Bot changed the base branch from graphite-base/742 to main February 25, 2026 03:44
@graphite-app graphite-app Bot force-pushed the 02-24-perf_do_not_reallocate_rule_context_struct_per-rule-per-file branch from 08523d8 to 9562f2d Compare February 25, 2026 03:44
@camchenry camchenry force-pushed the 02-24-perf_do_not_reallocate_rule_context_struct_per-rule-per-file branch from 9562f2d to 4b54cd9 Compare February 25, 2026 04:31
@camchenry camchenry marked this pull request as ready for review February 25, 2026 04:57
Comment thread internal/linter/linter.go
@camchenry camchenry force-pushed the 02-24-perf_do_not_reallocate_rule_context_struct_per-rule-per-file branch from 4b54cd9 to 22e16c3 Compare February 26, 2026 02:49
@camchenry camchenry requested a review from camc314 February 26, 2026 02:51
@camc314 camc314 self-assigned this Feb 26, 2026
Copy link
Copy Markdown
Contributor

@camc314 camc314 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice work!

Copy link
Copy Markdown
Contributor

camc314 commented Feb 26, 2026

Merge activity

graphite-app Bot pushed a commit that referenced this pull request Feb 26, 2026
~30% faster, ~48% less memory used, and ~37% fewer allocations on our simple benchmark. **IMPORTANT**: **this does _not_ mean that `oxlint` type-aware linting will run this much faster.** When I ran this with `oxlint`, it was not noticeably faster, it was maybe 1% better. But it was consistently faster, so I think it's worth merging. The intuition for the small real-world speedup is that the bottleneck is in the program creation and reading files from the filesystem, not the linting part itself.

The main idea is that we are currently re-allocating the rule context for every single file and every single rule. In most cases, though the behavior is exactly the same, we are just changing what file are rule we are linting with.

I refactored the diagnostic reporting code to be such that it never reallocates the diagnostic reporting functions: instead, we update the rule name and file and mutate the diagnostic reported with the current state after we've received it. This means we don't need to allocate this struct over and over again, which should reduce pressure on the GC quite a bit (this does show up in the benchmark too).

Before:

```
goos: darwin
goarch: arm64
pkg: github.com/typescript-eslint/tsgolint/cmd/tsgolint
cpu: Apple M1
BenchmarkAllRulesHeadless-8               	    4495	   2604683 ns/op	 7640387 B/op	   83526 allocs/op
BenchmarkAllRulesHeadless-8               	    4442	   2599917 ns/op	 7675330 B/op	   83833 allocs/op
BenchmarkAllRulesHeadless-8               	    4574	   2590637 ns/op	 7669025 B/op	   83771 allocs/op
BenchmarkAllRulesHeadless-8               	    4442	   2686293 ns/op	 7647700 B/op	   83587 allocs/op
BenchmarkAllRulesHeadless-8               	    4291	   2728212 ns/op	 7654198 B/op	   83647 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2041	   5863713 ns/op	 7629003 B/op	   83638 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2029	   5873142 ns/op	 7622800 B/op	   83581 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2034	   5863612 ns/op	 7614713 B/op	   83508 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2018	   5881180 ns/op	 7630698 B/op	   83649 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2038	   5890125 ns/op	 7635571 B/op	   83689 allocs/op
```

After:

```
goos: darwin
goarch: arm64
pkg: github.com/typescript-eslint/tsgolint/cmd/tsgolint
cpu: Apple M1
BenchmarkAllRulesHeadless-8               	    6270	   1827035 ns/op	 4372362 B/op	   52719 allocs/op
BenchmarkAllRulesHeadless-8               	    6024	   1794715 ns/op	 4372635 B/op	   52719 allocs/op
BenchmarkAllRulesHeadless-8               	    6276	   1795347 ns/op	 4354735 B/op	   52559 allocs/op
BenchmarkAllRulesHeadless-8               	    6327	   1837421 ns/op	 4389465 B/op	   52866 allocs/op
BenchmarkAllRulesHeadless-8               	    6244	   1863864 ns/op	 4375612 B/op	   52741 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2756	   4325317 ns/op	 3948056 B/op	   52395 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2791	   4327110 ns/op	 3953848 B/op	   52463 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2770	   4336311 ns/op	 3924766 B/op	   52215 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2758	   4342168 ns/op	 3948428 B/op	   52401 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2726	   4339023 ns/op	 3954526 B/op	   52460 allocs/op
```
@graphite-app graphite-app Bot force-pushed the 02-24-perf_do_not_reallocate_rule_context_struct_per-rule-per-file branch from 22e16c3 to a1c5ecd Compare February 26, 2026 12:20
~30% faster, ~48% less memory used, and ~37% fewer allocations on our simple benchmark. **IMPORTANT**: **this does _not_ mean that `oxlint` type-aware linting will run this much faster.** When I ran this with `oxlint`, it was not noticeably faster, it was maybe 1% better. But it was consistently faster, so I think it's worth merging. The intuition for the small real-world speedup is that the bottleneck is in the program creation and reading files from the filesystem, not the linting part itself.

The main idea is that we are currently re-allocating the rule context for every single file and every single rule. In most cases, though the behavior is exactly the same, we are just changing what file are rule we are linting with.

I refactored the diagnostic reporting code to be such that it never reallocates the diagnostic reporting functions: instead, we update the rule name and file and mutate the diagnostic reported with the current state after we've received it. This means we don't need to allocate this struct over and over again, which should reduce pressure on the GC quite a bit (this does show up in the benchmark too).

Before:

```
goos: darwin
goarch: arm64
pkg: github.com/typescript-eslint/tsgolint/cmd/tsgolint
cpu: Apple M1
BenchmarkAllRulesHeadless-8               	    4495	   2604683 ns/op	 7640387 B/op	   83526 allocs/op
BenchmarkAllRulesHeadless-8               	    4442	   2599917 ns/op	 7675330 B/op	   83833 allocs/op
BenchmarkAllRulesHeadless-8               	    4574	   2590637 ns/op	 7669025 B/op	   83771 allocs/op
BenchmarkAllRulesHeadless-8               	    4442	   2686293 ns/op	 7647700 B/op	   83587 allocs/op
BenchmarkAllRulesHeadless-8               	    4291	   2728212 ns/op	 7654198 B/op	   83647 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2041	   5863713 ns/op	 7629003 B/op	   83638 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2029	   5873142 ns/op	 7622800 B/op	   83581 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2034	   5863612 ns/op	 7614713 B/op	   83508 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2018	   5881180 ns/op	 7630698 B/op	   83649 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2038	   5890125 ns/op	 7635571 B/op	   83689 allocs/op
```

After:

```
goos: darwin
goarch: arm64
pkg: github.com/typescript-eslint/tsgolint/cmd/tsgolint
cpu: Apple M1
BenchmarkAllRulesHeadless-8               	    6270	   1827035 ns/op	 4372362 B/op	   52719 allocs/op
BenchmarkAllRulesHeadless-8               	    6024	   1794715 ns/op	 4372635 B/op	   52719 allocs/op
BenchmarkAllRulesHeadless-8               	    6276	   1795347 ns/op	 4354735 B/op	   52559 allocs/op
BenchmarkAllRulesHeadless-8               	    6327	   1837421 ns/op	 4389465 B/op	   52866 allocs/op
BenchmarkAllRulesHeadless-8               	    6244	   1863864 ns/op	 4375612 B/op	   52741 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2756	   4325317 ns/op	 3948056 B/op	   52395 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2791	   4327110 ns/op	 3953848 B/op	   52463 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2770	   4336311 ns/op	 3924766 B/op	   52215 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2758	   4342168 ns/op	 3948428 B/op	   52401 allocs/op
BenchmarkAllRulesHeadlessSingleThread-8   	    2726	   4339023 ns/op	 3954526 B/op	   52460 allocs/op
```
@graphite-app graphite-app Bot force-pushed the 02-24-perf_do_not_reallocate_rule_context_struct_per-rule-per-file branch from a1c5ecd to 502890a Compare February 26, 2026 12:25
@graphite-app graphite-app Bot merged commit 502890a into main Feb 26, 2026
8 checks passed
@graphite-app graphite-app Bot deleted the 02-24-perf_do_not_reallocate_rule_context_struct_per-rule-per-file branch February 26, 2026 12:32
@graphite-app graphite-app Bot removed the 0-merge label Feb 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants