Skip to content

Conversation

@ToddGrun
Copy link
Contributor

This method was creating a new StringWriter on every invocation. On net framework, each StringWriter incurs an additional char array allocation during construction. Instead, add a pool for stringwriters in GreenNode to avoid these allocations on every call. This reduced allocations spent under this method during the typing scenario in the razor lsp speedometer test from 3.5% to 1.9%.

Note that it would be nice to have GreenNode.ToString use this optimization too, however it differed (intentionally?) from the caller in SyntaxNodeExtensions.GetContent by passing in the invariant culture to the StringWriter during construction. I don't see ToString called near as often in the profile I'm looking at, so, it didn't seem to warrant creating a separate pool for that method. I would appreciate verification though that it's intentional that these two calls use different culture semantics.

Speedometer run: https://dev.azure.com/devdiv/DevDiv/_apps/hub/ms-vseng.pit-vsengPerf.pit-hub?targetBuild=10711.132.dn-bot.250612.052736.643013&targetBranch=main&targetPerfBuildId=11746547&runGroup=Speedometer&baselineBuild=10711.132&baselineBranch=main

Allocations before change
image

Allocations with change
image

This method was creating a new StringWriter on every invocation. On net framework, each StringWriter incurs an additional char array allocation during construction. Instead, add a pool for stringwriters in GreenNode to avoid these allocations on every call. This reduced allocations spent under this method during the typing scenario in the razor lsp speedometer test from 3.5% to 1.9%.

Note that it would be nice to have GreenNode.ToString use this optimization too, however it differed (intentionally?) from the caller in SyntaxNodeExtensions.GetContent by passing in the invariant culture to the StringWriter during construction. I don't see ToString called near as often in the profile I'm looking at, so, it didn't seem to warrant creating a separate pool for that method. I would appreciate verification though that it's intentional that these two calls use different culture semantics.

Speedometer run: https://dev.azure.com/devdiv/DevDiv/_apps/hub/ms-vseng.pit-vsengPerf.pit-hub?targetBuild=10711.132.dn-bot.250612.052736.643013&targetBranch=main&targetPerfBuildId=11746547&runGroup=Speedometer&baselineBuild=10711.132&baselineBranch=main
@ToddGrun ToddGrun requested a review from a team as a code owner June 12, 2025 13:56
public static string GetContent<TNode>(this TNode node) where TNode : SyntaxNode
{
using var _ = StringBuilderPool.GetPooledObject(out var builder);
using var writer = new System.IO.StringWriter(builder);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

StringWriter(builder)

Here's where GetContent implicity specified CultureInfo.CurrentCulture

Copy link
Member

Choose a reason for hiding this comment

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

I really don't think you need to worry about CultureInfo for the StringWriter. GreenNode.WriteTo delegates to GreenNode.WriteTokenTo to do any actually writing. WriteTokenTo is only overridden by SyntaxToken and it just writes a string. IOW, nothing actually writes anything other than strings, so it's essentially culture-neutral.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's what I was hoping. Went ahead and changed to always use InvariantCulture. Thanks!

return builder.ToString();
}

internal string ToStringInCurrentCulture()
Copy link
Contributor Author

@ToddGrun ToddGrun Jun 12, 2025

Choose a reason for hiding this comment

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

Right above here is where ToString instead specifies InvariantCulture.

If these were to match, the ToStringInCurrentCulture change could be moved to ToString.

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