Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(linter): make all rules share a diagnostics vec #5806

Conversation

DonIsaac
Copy link
Collaborator

@DonIsaac DonIsaac commented Sep 16, 2024

What This PR Does

Each time a lint rule is run on a file, it will now store diagnostics in a
shared vec instead of having its own list. This is done by moving diagnostics
from LintContext to ContextHost.

The motivating line of code can be found in Linter::run in
oxc_linter/src/lib.rs#145

rules.into_iter().flat_map(|(_, ctx)| ctx.into_message()).collect::<Vec<_>>()

Each LintContext allocates a new vec, and pushes diagnostics into it. After
all run-related methods have been run, a new vec is created and those
diagnostics are copied into it. This is O(n+1) allocations and O(n) copies,
not to mention that allocations for the original vecs cannot be re-used since
those vecs aren't dropped until after the new one is created.

Copy link

graphite-app bot commented Sep 16, 2024

Your org has enabled the Graphite merge queue for merging into main

Add the label “0-merge” to the PR and Graphite will automatically add it to the merge queue when it’s ready to merge. Or use the label “hotfix” to add to the merge queue as a hot fix.

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

@DonIsaac DonIsaac added the C-performance Category - Solution not expected to change functional behavior, only performance label Sep 16, 2024 — with Graphite App
@DonIsaac DonIsaac marked this pull request as ready for review September 16, 2024 14:35
@github-actions github-actions bot added the A-linter Area - Linter label Sep 16, 2024
@DonIsaac DonIsaac force-pushed the don/09-16-perf_linter_make_all_rules_share_a_diagnostics_vec branch from fa2534a to 52696ff Compare September 16, 2024 14:36
Copy link

codspeed-hq bot commented Sep 16, 2024

CodSpeed Performance Report

Merging #5806 will not alter performance

Comparing don/09-16-perf_linter_make_all_rules_share_a_diagnostics_vec (3725d5d) with main (e04841c)

Summary

✅ 29 untouched benchmarks

@overlookmotel
Copy link
Collaborator

overlookmotel commented Sep 16, 2024

Just to check my understanding of the linter is correct:

  • For each individual file, all rules run on the same thread.
  • Each file may run on a different thread.

Do I have that right? If so, this change does make some sense.

But... benchmarks seem to show this actually making things slightly slower rather than faster (-0.2% to -0.5% change). It's hard to tell if that's just noise in benchmarks. You may want to add a dummy commit to re-run the benchmarks, and then revert the commit to run them again. If the perf drop shows consistently across 3 runs, then it's probably real.

0.5% is not much, so it's no big deal either way. But it seems counter-productive to make a change specifically to improve perf, if it actually does the opposite.

If the perf drop is real, explanation may be that the additional cost of RefCell is outweighing the gain of reducing memory copies.

Presumably in real world usage, most rules will report 0 errors on 99% of files, because the programmer will fix (or disable) any lint errors pretty quickly. And if there are no errors, then there's no memory to copy, so this change doesn't gain anything, but on negative side it incurs the cost of RefCells.

(We could have our cake and eat it by replacing RefCell with zero-cost GhostCell, but that's a bit of an undertaking due to the weird lifetimes.)

PS: Unrelated, but I also wonder if initial capacity of 128 is too high. Do files in general produce that many errors? If I'm right that 0 is the most common number of errors, it might be better not to allocate any initial capacity at all.

@DonIsaac
Copy link
Collaborator Author

Just to check my understanding of the linter is correct:

  • For each individual file, all rules run on the same thread.
  • Each file may run on a different thread.

Do I have that right? If so, this change does make some sense.

Yes

But... benchmarks seem to show this actually making things slightly slower rather than faster (-0.2% to -0.5% change). It's hard to tell if that's just noise in benchmarks. You may want to add a dummy commit to re-run the benchmarks, and then revert the commit to run them again. If the perf drop shows consistently across 3 runs, then it's probably real.

0.5% is not much, so it's no big deal either way. But it seems counter-productive to make a change specifically to improve perf, if it actually does the opposite.

I'll investigate those regressions. Locally, I was seeing a 7% perf improvement on the RadixUIAdoptionSection benchmark. Maybe it has to do with the number of rules?

PS: Unrelated, but I also wonder if initial capacity of 128 is too high. Do files in general produce that many errors? If I'm right that 0 is the most common number of errors, it might be better not to allocate any initial capacity at all.

I'll play around with the numbers and see what works best

@DonIsaac DonIsaac force-pushed the don/09-16-perf_linter_make_all_rules_share_a_diagnostics_vec branch from 52696ff to 09a4b31 Compare September 16, 2024 22:27
@DonIsaac
Copy link
Collaborator Author

DonIsaac commented Sep 16, 2024

I got a ~0.8% improvement by increasing the initial capacity to 512. This makes sense, as before each rule got a list with an initial capacity of 128.

@DonIsaac
Copy link
Collaborator Author

...And now it's even worse in CI... that's odd, I'll keep looking into it.

@Boshen Boshen added the 0-merge Merge with Graphite Merge Queue label Sep 17, 2024 — with Graphite App
Copy link

graphite-app bot commented Sep 17, 2024

Merge activity

## What This PR Does

Each time a lint rule is run on a file, it will now store diagnostics in a
shared vec instead of having its own list. This is done by moving `diagnostics`
from `LintContext` to `ContextHost`.

The motivating line of code can be found in `Linter::run` in
`oxc_linter/src/lib.rs#145`

```rust
rules.into_iter().flat_map(|(_, ctx)| ctx.into_message()).collect::<Vec<_>>()
```

Each `LintContext` allocates a new vec, and pushes diagnostics into it. After
all run-related methods have been run, a new vec is created and those
diagnostics are copied into it. This is `O(n+1)` allocations and `O(n)` copies,
not to mention that allocations for the original vecs cannot be re-used since
those vecs aren't dropped until after the new one is created.
@Boshen Boshen force-pushed the don/09-16-perf_linter_make_all_rules_share_a_diagnostics_vec branch from 09a4b31 to 3725d5d Compare September 17, 2024 05:46
@graphite-app graphite-app bot merged commit 3725d5d into main Sep 17, 2024
26 checks passed
@graphite-app graphite-app bot deleted the don/09-16-perf_linter_make_all_rules_share_a_diagnostics_vec branch September 17, 2024 05:50
@overlookmotel
Copy link
Collaborator

@DonIsaac I'm not sure if you intended this to be merged, given that you were still investigating.

Locally, I was seeing a 7% perf improvement on the RadixUIAdoptionSection benchmark.

This is really odd, and worth investigating. If our CodSpeed benchmarks are that far off reality, we should try to fix that (or at least understand in what circumstances it happens).

When you ran it locally, did you run with the same rules enabled as on CI? (i.e. was it exact like-for-like comparison?)

How many errors does running all the rules on RadixUIAdoptionSection produce?

If my assumption is correct that in real world most commonly the linter will produce 0 errors, and our benchmark fixtures produce loads, then perhaps we need new benchmark fixtures!

@oxc-bot oxc-bot mentioned this pull request Sep 18, 2024
Boshen added a commit that referenced this pull request Sep 18, 2024
## [0.9.6] - 2024-09-18

### Features

- 3bf7b24 linter: Make `typescript/no-duplicate-enum-values` a
`correctness` rule (#5810) (DonIsaac)
- 7799c06 linter/react: Implement `no-danger-with-children` rule (#5420)
(Cam McHenry)

### Bug Fixes

- f942485 linter: Remove all* remaining "Disallow <foo>" messages
(#5812) (DonIsaac)
- b5ad518 linter: Improve diagnostic messages for various lint rules
(#5808) (DonIsaac)
- 858f7af linter: Plugin prefix name for eslint-plugin-node (#5807)
(DonIsaac)
- 737ba1d linter: Fix some cases on ```AssignmentExpression``` for
```unicorn/consistent-function-scoping``` (#5675) (Arian94)
- 148c7a8 linter: Replace bitwise AND (&) with logical AND (&&) in
explici… (#5780) (kaykdm)
- b4ed564 linter/no-unused-vars: Writes to members triggering false
positive (#5744) (Dunqing)
- e9c084a linter/no-unused-vars: False positive when a variable used as
a computed member property (#5722) (Dunqing)

### Performance

- 3725d5d linter: Make all rules share a diagnostics vec (#5806)
(DonIsaac)
- e978567 linter: Shrink size of `DisableDirectives` (#5798) (DonIsaac)
- 1bfa515 linter: Remove redundant clone of diagnostics in context
(#5797) (DonIsaac)
- e413cad linter: Move shared context info to `ContextHost` (#5795)
(DonIsaac)

### Refactor

- 6dd6f7c ast: Change `Comment` struct (#5783) (Boshen)
- 7caae5b codegen: Add `GetSpan` requirement to `Gen` trait (#5772)
(Boshen)
- 026ee6a linter: Decouple module resolution from import plugin (#5829)
(dalaoshu)
- 50834bc linter: Move `override_rule` to `OxlintRules` (#5708)
(DonIsaac)
- a438743 linter: Move `OxlintConfig` to `Oxlintrc` (#5707) (DonIsaac)
- f61e8b5 linter: Impl serde and schemars traits for `LintPlugins`
(#5706) (DonIsaac)
- 20a7861 linter: Shorten `Option` syntax (#5735) (overlookmotel)
- d8b612c oxc_linter: Prefer pass Enum instead of str `no_plus_plus`
(#5730) (IWANABETHATGUY)
- cc0408b semantic: S/AstNodeId/NodeId (#5740) (Boshen)

---------

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
0-merge Merge with Graphite Merge Queue A-linter Area - Linter C-performance Category - Solution not expected to change functional behavior, only performance
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants