-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Cache diagnostic analyzer computation #80045
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| using System.Collections.Immutable; | ||
| using System.Runtime.CompilerServices; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.CodeAnalysis.PooledObjects; | ||
|
|
||
| namespace Microsoft.CodeAnalysis.Diagnostics; | ||
|
|
||
| internal sealed partial class DiagnosticAnalyzerService | ||
| { | ||
| /// <summary> | ||
| /// A cache from DiagnosticAnalyzer to whether or not it is a candidate for deprioritization when lightbulbs | ||
| /// compute diagnostics for a particular priority class. Note: as this caches data, it may technically be | ||
| /// inaccurate as things change in the system. For example, this is based on the registered actions made | ||
| /// by an analyzer. Hypothetically, such an analyzer might register different actions based on on things | ||
| /// like appearing in a different language's compilation, or a compilation with different references, etc. | ||
| /// We accept that this cache may be inaccurate in such scenarios as they are likely rare, and this only | ||
| /// serves as a simple heuristic to order analyzer execution. If wrong, it's not a major deal. | ||
| /// </summary> | ||
| private static readonly ConditionalWeakTable<DiagnosticAnalyzer, StrongBox<bool>> s_analyzerToIsDeprioritizationCandidateMap = new(); | ||
|
|
||
| private async Task<ImmutableArray<DiagnosticAnalyzer>> GetDeprioritizationCandidatesInProcessAsync( | ||
| Project project, ImmutableArray<DiagnosticAnalyzer> analyzers, CancellationToken cancellationToken) | ||
| { | ||
| using var _ = ArrayBuilder<DiagnosticAnalyzer>.GetInstance(out var builder); | ||
|
|
||
| HostAnalyzerInfo? hostAnalyzerInfo = null; | ||
| CompilationWithAnalyzersPair? compilationWithAnalyzers = null; | ||
|
|
||
| foreach (var analyzer in analyzers) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this loop is virtually the same. just we avoid any work if we've already computed and cached the value for an analyzer in the past. |
||
| { | ||
| if (!s_analyzerToIsDeprioritizationCandidateMap.TryGetValue(analyzer, out var boxedBool)) | ||
| { | ||
| if (hostAnalyzerInfo is null) | ||
| { | ||
| hostAnalyzerInfo = GetOrCreateHostAnalyzerInfo(project); | ||
| compilationWithAnalyzers = await GetOrCreateCompilationWithAnalyzersAsync( | ||
| project, analyzers, hostAnalyzerInfo, this.CrashOnAnalyzerException, cancellationToken).ConfigureAwait(false); | ||
| } | ||
|
|
||
| boxedBool = new(await IsCandidateForDeprioritizationBasedOnRegisteredActionsAsync(analyzer).ConfigureAwait(false)); | ||
| #if NET | ||
| s_analyzerToIsDeprioritizationCandidateMap.TryAdd(analyzer, boxedBool); | ||
| #else | ||
| lock (s_analyzerToIsDeprioritizationCandidateMap) | ||
| { | ||
| if (!s_analyzerToIsDeprioritizationCandidateMap.TryGetValue(analyzer, out var existing)) | ||
| s_analyzerToIsDeprioritizationCandidateMap.Add(analyzer, boxedBool); | ||
| } | ||
| #endif | ||
| } | ||
|
|
||
| if (boxedBool.Value) | ||
| builder.Add(analyzer); | ||
| } | ||
|
|
||
| return builder.ToImmutableAndClear(); | ||
|
|
||
| async Task<bool> IsCandidateForDeprioritizationBasedOnRegisteredActionsAsync(DiagnosticAnalyzer analyzer) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this method is unchanged. |
||
| { | ||
| // We deprioritize SymbolStart/End and SemanticModel analyzers from 'Normal' to 'Low' priority bucket, | ||
| // as these are computationally more expensive. | ||
| // Note that we never de-prioritize compiler analyzer, even though it registers a SemanticModel action. | ||
| if (compilationWithAnalyzers == null || | ||
| analyzer.IsWorkspaceDiagnosticAnalyzer() || | ||
| analyzer.IsCompilerAnalyzer()) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| var telemetryInfo = await compilationWithAnalyzers.GetAnalyzerTelemetryInfoAsync(analyzer, cancellationToken).ConfigureAwait(false); | ||
| if (telemetryInfo == null) | ||
| return false; | ||
|
|
||
| return telemetryInfo.SymbolStartActionsCount > 0 || telemetryInfo.SemanticModelActionsCount > 0; | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just want to make sure this is a sufficient key, as the result of IsCandidateForDeprioritizationBasedOnRegisteredActionsAsync is determined by both the analyzer and compilationWithAnalyzers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I will doc. This is all part of a heuristics system that attempts to tell if an analyzer should be deprioritized when computing lightbulb actions. really all it does is ask "does this analyzer register any SymbolStart/SemanticModel actions". If so, then it should be deprioritized. So the only thing that matters here is if the same DiagAnalyzer instance might produce a different result when computed against a different compilation.
Teh answer is "yes it might". examples could include if this registered different actions in a VBComp vs a C# one (for DiagAnalyzers taht work on both langs), or if it sometimes registered those actions and sometimes did not, based on some aspect of the compilation it ran against.
However, the likelihood of this happening is super small. And in that case, all that that means is that we run the analyzer at a different priority class than we would otehrwise run it.
Again, in all cases, we run all analyzers. This jus taffects how we order them. And so being slightly wrong (because we cahced some earlier data that is EXTREMELY unlikely to ever change within a run of VS) is fine.