Skip to content

Cache indent strings in CodeWriter#12366

Merged
ToddGrun merged 5 commits intodotnet:mainfrom
ToddGrun:dev/toddgrun/IndentationCache
Oct 21, 2025
Merged

Cache indent strings in CodeWriter#12366
ToddGrun merged 5 commits intodotnet:mainfrom
ToddGrun:dev/toddgrun/IndentationCache

Conversation

@ToddGrun
Copy link
Contributor

There are typically only a few different sizes requested, and useTabs/tabSize very rarely change. Locally, I only saw eight unique combinations of these values when opening and typing in orchardcore.

ComputeIndent accounts for 11.0 MB (0.4%) of total allocations and 27 ms in in the ScrollingAndTypingInCohosting speedometer test. With this change, the cost under IndentCache.GetIndentString goes to 0.0 MB (0.0%) and 13 ms respectively.

Test insertion: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/680751

There are typically only a few different sizes requested, and useTabs/tabSize *very* rarely change. Locally, I only saw eight unique combinations of these values when opening and typing in orchardcore.

ComputeIndent accounts for 11.0 MB (0.4%) of total allocations and 27 ms in in the ScrollingAndTypingInCohosting speedometer test. With this change, those numbers go to 0.0 MB (0.0%) and 13 ms respectively.

Test insertion: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/680751
@ToddGrun ToddGrun requested a review from a team as a code owner October 18, 2025 22:35
Copy link
Member

@DustinCampbell DustinCampbell left a comment

Choose a reason for hiding this comment

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

Nice work! I think more can be done to reduce allocations here though.

Comment on lines +34 to +35
s_tabs.Span[..tabCount].CopyTo(destination);
s_spaces.Span[..spaceCount].CopyTo(destination[tabCount..]);
Copy link
Member

Choose a reason for hiding this comment

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

I think this will probably break on existing code somewhere. These are going to throw if tabCount if greater than 64 or spaceCount is greater than 128. That's what the while loops and Math.Min(...) calls handled in my suggested implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice catch! I think commit 3 should address that. Tests added. If you find a bug in it, I'll concede.

…either tabs/spaces counts exceeded corresponding cached string length.
Copy link
Member

@davidwengier davidwengier left a comment

Choose a reason for hiding this comment

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

Well this changed a lot while I was asleep. Pretty cool!

Comment on lines +37 to +38
var tabs = SliceOrCreate(tabCount, s_tabs);
var spaces = SliceOrCreate(spaceCount, s_spaces);
Copy link
Member

Choose a reason for hiding this comment

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

However unlikely, it doesn't feel right to potentially allocate strings inside of a call to string.Create(...) since that's intended for allocation-free string creation. On the one hand, it does read as much simpler but that makes the potential allocation even more subtle. Personally, I probably wouldn't have cut this particular corner, but I do understand your thinking.

Comment on lines +40 to +41
tabs.Span.CopyTo(destination);
spaces.Span.CopyTo(destination[tabCount..]);
Copy link
Member

@DustinCampbell DustinCampbell Oct 21, 2025

Choose a reason for hiding this comment

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

Did you consider using Span<T>.Fill(...)? It feels like the body of this lambda could be replaced with:

destination[..tabCount].Fill('\t');
destination[tabCount..].Fill(' ');

That'd perform vectorized writes without allocating a new string.

Copy link
Member

Choose a reason for hiding this comment

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

FYI, that's essentially what new string('\t', tabCount) does to fill the string contents.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oooh, that's beautiful!

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