Reduce CPU costs under AnalyzerExecutor.ExecuteSyntaxNodeActions#76894
Conversation
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.
src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.GroupedAnalyzerActions.cs
Outdated
Show resolved
Hide resolved
src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.GroupedAnalyzerActions.cs
Outdated
Show resolved
Hide resolved
|
Moving out of draft status as the speedometer numbers came back pretty good. @dotnet/roslyn-compiler -- ptal |
src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.GroupedAnalyzerActions.cs
Show resolved
Hide resolved
src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.GroupedAnalyzerActions.cs
Outdated
Show resolved
Hide resolved
|
@dotnet/roslyn-compiler -- ptal |
| /// 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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
A major win? Or a narrow win? I feel like I could read the typo either way :P
|
@dotnet/roslyn-compiler for second review. Thanks |
|
Does this change mean that in
Not saying there is any need to add such an assertion, just trying to make sure I grasp the impact/nature of the change. |
|
Yes. I believe that should be the case. I can add such an assert in my follow-up |
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 ***

*** after changes ***
