Improve performance of SyntaxReplacer.ReplaceNode #77314
+120
−23
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
I've noticed overrides completion commit in the csharp completion speedometer profiles as indicating a large amount of time/memory spent in their ReplaceNode calls. (about 18.6% of CPU time and 24.0% of memory allocations spent within CommitManager.TryCommit are attributable to ReplaceNode calls). Taking a look at the method, there were two major inefficiencies:
The Visit methods for Nodes/Tokens/Trivia could be changed such that if they are invoked for an item affecting _totalSpan, that once they are processed they should no longer have an effect on _totalSpan. This would allow a large pruning of the amount of tree walked due to the ShouldVisit calls. Override completion benefits in particular as it replaces a large single node in the tree (the class node) and thus wouldn't need to walk inside the class anymore.
SyntaxList didn't override VisitList, and thus used the base class implementation which was inefficient for several reasons as outlined in added VisitList method. The initial concern here was the member red node realization, but several other perf optimizations were added as well.
As the images below show, these optimizations reduced the CPU/allocations costs of the ReplaceNode call to basically nothing. Note that I didn't get a perf run on just commit 5, so I'm unsure of the breakdown into how much item 1 and item 2 from above contribute to the performance increase.
Commit 2 test run didn't show much improvement
Commit 3 test run didn't show much improvement
Commit 4 test run didn't show much improvement
Commit 5 test insertion: Didn't run because of a pipeline "feature"
Commit 6 test insertion: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/613403
*** Before CPU ***

*** After CPU ***

*** Before Allocations ***

*** After Allocations ***
