Skip to content

Reduce string allocations in ParseUsingDeclaration by replacing LINQ + Concat(this WorkspaceEdit, WorkspaceEdit?) with StringBuilder loop#12774

Merged
davidwengier merged 3 commits intodotnet:mainfrom
nareshjo:ParseUsingDeclaration-StrAllocations
Feb 13, 2026
Merged

Conversation

@nareshjo
Copy link
Copy Markdown

@nareshjo nareshjo commented Feb 12, 2026

🤖 AI-Generated Pull Request 🤖

This pull request was generated by the VS Perf Rel AI Agent. Please review this AI-generated PR with extra care! For more information, visit our wiki. Please share feedback with TIP Insights

  • Issue: ParseUsingDeclaration builds two content strings by chaining .Skip(), .Where(), .Select() LINQ extensions over a SyntaxToken[] and passing the results to string.Concat(IEnumerable<string>). Each string.Concat call internally allocates a StringBuilder, iterates through multiple LINQ enumerator objects (one per .Skip(), .Where(), .Select()), and calls StringBuilder.ToString() to allocate the final string. This creates 4+ enumerator objects and 2 intermediate StringBuilder instances per @using directive parse — a hot path triggered on every keystroke.

    This matches the allocation stack flow showing ParseMarkupNodesParseCodeTransitionOtherParserBlockParseBlockTryParseKeywordParseUsingKeywordParseUsingDeclarationString.ConcatStringBuilder.ToStringFramedAllocateStringTypeAllocated!System.String

microsoft.codeanalysis.razor.compiler.dll!Microsoft.AspNetCore.Razor.Language.Legacy.CSharpCodeParser.ParseUsingDeclaration
mscorlib.dll!System.String.Concat
mscorlib.dll!System.Text.StringBuilder.ToString
clr.dll!FramedAllocateString
clr.dll!SVR::GCHeap::Alloc
clr.dll!SVR::gc_heap::try_allocate_more_space
TypeAllocated!System.String
...
 TypeAllocated!SZGenericArrayEnumerator`1[Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax.SyntaxToken]
...
TypeAllocated!WhereEnumerableIterator`1[Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax.SyntaxToken]
  • Issue type: Eliminate unnecessary LINQ enumerator and string.Concat allocations in hot path

  • Proposed fix: Replace the two LINQ chains (.Skip().Where().Select()) + string.Concat(IEnumerable<string>) calls with a single for loop over the already-snapshotted SyntaxToken[] array, appending to two plain StringBuilder instances. The TokenBuilder.ToList().Nodes snapshot is kept at its original position (before TryAccept mutates TokenBuilder) to preserve exact functional parity. This eliminates 4+ LINQ enumerator allocations and avoids string.Concat's internal StringBuilder allocation, while producing identical output strings.

Best practices wiki
See related failure in PRISM
ADO work item

…+ Concat(this WorkspaceEdit, WorkspaceEdit?) with StringBuilder loop
@nareshjo nareshjo requested a review from a team as a code owner February 12, 2026 23:05
Copy link
Copy Markdown
Contributor

@ToddGrun ToddGrun left a comment

Choose a reason for hiding this comment

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

:shipit:

@davidwengier
Copy link
Copy Markdown
Member

Thanks @nareshjo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants