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

Reduce CPU costs under AnalyzerExecutor.ExecuteSyntaxNodeActions #76894

Conversation

ToddGrun
Copy link
Contributor

@ToddGrun ToddGrun commented Jan 23, 2025

In the scrolling speedometer test, the Roslyn CodeAnalysis process shows about 25% of CPU spent in this method. Of that, a surprising amount of it (11.2%) is spent in ImmutableSegmentedDictionary.TryGetValue. Debugging through this code, it appears this is because that is called O(m x n) times where m is the number of nodes to analyze and n is the number of items in groupedActions.GroupedActionsByAnalyzer.

Instead, add a hook into GroupedAnalyzerActions to allow a mapping of kind -> analyzers. This can be used by executeNodeActionsByKind to get a much quicker way to determine whether the analyzer can contribute for the node in question.

*** before changes ***
image

*** after changes ***
image

In the scrolling speedometer test, the Roslyn CodeAnalysis process shows about 25% of CPU spent in this method. Of that, a surprising amount of it (11.2%) is spent in ImmutableSegmentedDictionary.TryGetValue. Debugging through this code, it appears this is because that is called O(m x n) times where m is the number of nodes to analyze and n is the number of items in groupedActions.GroupedActionsByAnalyzer.

Instead, add a hook into GroupedAnalyzerActions to allow a mapping of kind -> analyzers. This can be used by executeNodeActionsByKind to get a much quicker way to determine whether the analyzer can contribute for the node in question.

Only publishing this early so Cyrus can take a peek, as I still need to do a bit of debugging around these changes. Once Cyrus and I think the changes have merit, I will create a test insertion and publish the speedometer results once those are available. Only if all that goes well will I promote this PR out of draft mode.
@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Analyzers untriaged Issues and PRs which have not yet been triaged by a lead labels Jan 23, 2025
@ToddGrun
Copy link
Contributor Author

ToddGrun commented Jan 24, 2025

@ToddGrun ToddGrun changed the title WIP: Reduce CPU costs under AnalyzerExecutor.ExecuteSyntaxNodeActions Reduce CPU costs under AnalyzerExecutor.ExecuteSyntaxNodeActions Jan 25, 2025
@ToddGrun ToddGrun marked this pull request as ready for review January 25, 2025 05:58
@ToddGrun ToddGrun requested a review from a team as a code owner January 25, 2025 05:58
@ToddGrun
Copy link
Contributor Author

Moving out of draft status as the speedometer numbers came back pretty good.

@dotnet/roslyn-compiler -- ptal

@CyrusNajmabadi CyrusNajmabadi self-requested a review January 25, 2025 06:33
@ToddGrun
Copy link
Contributor Author

@dotnet/roslyn-compiler -- ptal

@RikkiGibson RikkiGibson self-assigned this Jan 27, 2025
/// cref="ArrayBuilder{T}.ToImmutableAndFree"/>. The <paramref name="dictionary"/> will be freed at the end of
/// this method as well, and should not be used afterwards.
/// </summary>
public static ImmutableSegmentedDictionary<K, ImmutableArray<V>> ToImmutableSegmentedDictionaryAndFree<K, V>(this PooledDictionary<K, ArrayBuilder<V>> dictionary)
Copy link
Member

Choose a reason for hiding this comment

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

this just helps with code hygiene around repetition.

return new GroupedAnalyzerActions(newGroupedActions, analyzersByKind, newAnalyzerActions);
}

private static ImmutableSegmentedDictionary<TLanguageKindEnum, ImmutableArray<DiagnosticAnalyzer>> CreateAnalyzersByKind(ImmutableArray<(DiagnosticAnalyzer, GroupedAnalyzerActionsForAnalyzer)> groupedActionsAndAnalyzers)
Copy link
Member

Choose a reason for hiding this comment

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

this is a marow win by getting us to O(1) lookups per syntax node kind, which is an uber hot spot while doing analyzers.

Copy link
Contributor

Choose a reason for hiding this comment

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

A major win? Or a narrow win? I feel like I could read the typo either way :P

Copy link
Member

Choose a reason for hiding this comment

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

Lol. Major.

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM Thanks (iteration 7)

@jcouv
Copy link
Member

jcouv commented Jan 27, 2025

@dotnet/roslyn-compiler for second review. Thanks

@RikkiGibson
Copy link
Contributor

Does this change mean that in AnalyzerExecutor.ExecuteSyntaxNodeActions, an assertion like the following would hold:

Debug.Assert(nodesToAnalyze.Any(node => nodeActionsByKind.ContainsKey(getKind(node))));

Not saying there is any need to add such an assertion, just trying to make sure I grasp the impact/nature of the change.

@CyrusNajmabadi
Copy link
Member

Yes. I believe that should be the case. I can add such an assert in my follow-up

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Analyzers Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants