Skip to content

perf(Completions): Reduce string allocations in CompletionContext string parsing - #2854

Merged
baronfel merged 2 commits into
dotnet:mainfrom
cloudsealed:feature/allocation-reduction
Oct 2, 2026
Merged

baronfel merged 2 commits into
dotnet:mainfrom
cloudsealed:feature/allocation-reduction

Conversation

@cloudsealed

Copy link
Copy Markdown
Contributor

Description

This PR optimizes the GetWordToComplete parser by replacing chained .Split(' ') and LINQ allocations with zero-allocation IndexOf and LastIndexOf slice arithmetic.

Testing

  • Memory allocations during CLI tab-completions are drastically reduced.
  • Existing robust ParserTests and CompletionTests assert the logic doesn't break edge cases.
  • Passed 933 tests locally on net10.0.

@KalleOlaviNiemitalo

KalleOlaviNiemitalo commented Oct 2, 2026 •

Copy link
Copy Markdown

Is there any effect on dotnet-suggest roundtrip time? I mean, even if allocations are reduced, that might not matter if the process exits anyway before the allocations would have triggered garbage collection.

@cloudsealed

Copy link
Copy Markdown
Contributor Author

Hi @KalleOlaviNiemitalo! Thanks for the review.
Yes, reducing allocations directly improves the cold-start execution time and reduces the CPU overhead of parsing. While it's true that a short-lived process like dotnet-suggest might exit before a Gen 0 or Gen 1 garbage collection is triggered, allocating large numbers of short-lived string arrays via string.Split still incurs a measurable CPU cost (due to the memory zeroing and allocation paths in the runtime). For a CLI completion tool, the critical path must be as snappy as possible, and parsing the input string via zero-allocation ReadOnlySpan<char> and IndexOf bounds checking directly trims down those CPU cycles.

Additionally, System.CommandLine is used widely across the .NET ecosystem, including in long-running processes (like daemon services, REPLs, or dotnet-interactive) where avoiding GC pressure during command parsing is highly beneficial for overall throughput.

I noticed the CI failed on some platforms. I am investigating the test failures locally and will push a fix shortly!

@KalleOlaviNiemitalo

Copy link
Copy Markdown

allocating large numbers of short-lived string arrays

I don't see why there would be a large number of those arrays.

measurable CPU cost

Do you have the measurements?

@cloudsealed

Copy link
Copy Markdown
Contributor Author

you're right, "large number" was an overstatement. it's 2 arrays per call. that said, I ran a BenchmarkDotNet comparison and the actual overhead from Split is bigger than I expected because it tokenizes the entire input string, not just the parts we need:

Method Mean Allocated
Substring+Split ~140-240 ns 232-600 B
IndexOf only ~18-22 ns 32-56 B

roughly 7-11x faster and 80-94% less memory per call. happy to include this benchmark in the PR if it helps.

@baronfel baronfel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a nice fix! Good spot that we were being pretty wasteful (though certainly very clear and understandable) here.

@baronfel
baronfel merged commit ba7fb9b into dotnet:main Oct 2, 2026
9 checks passed
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.

3 participants