Skip to content

Compiler: Avoid allocating strings with writing integer literals#12101

Merged
DustinCampbell merged 2 commits intodotnet:mainfrom
DustinCampbell:write-integers-allocation-free
Aug 18, 2025
Merged

Compiler: Avoid allocating strings with writing integer literals#12101
DustinCampbell merged 2 commits intodotnet:mainfrom
DustinCampbell:write-integers-allocation-free

Conversation

@DustinCampbell
Copy link
Member

@DustinCampbell DustinCampbell commented Aug 12, 2025

When writing integer literals, the compiler first allocates a string using the invariant culture and then writes the string. This is pretty wasteful and the compiler writes a lot of integer literals for #line directives. This change introduces a new CodeWriter.WriteIntegerLiteral function that writes integers directly without allocations using a lookup table.


CI Build: https://dev.azure.com/dnceng/internal/_build/results?buildId=2770483&view=results
Test Insertion: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/661077
Toolset Run: https://dev.azure.com/dnceng/internal/_build/results?buildId=2770489&view=results

Whenever the Razor compiler writes a integer value, it calls `ToString(CultureInfo.InvariantCulture)` on the integer to allocate a new string. To avoid these extra strings, this change introduces a new `CodeWriter.WriteIntegerLiteral(int)` extension method that can directly write an integer value with invariant culture. This method uses a lookup table of precomputed string values to avoid allocations.
Use the `CodeWriter.WriteIntegerLiteral` extension method in the Razor compiler to avoid extra string allocations (where possible).
@DustinCampbell DustinCampbell requested a review from a team as a code owner August 12, 2025 20:01
@DustinCampbell DustinCampbell requested a review from a team August 12, 2025 22:40
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.

I wonder how long a test would take if you just use [CombinatorialData] :D

private static readonly ReadOnlyMemory<char> s_true = "true".AsMemory();
private static readonly ReadOnlyMemory<char> s_false = "false".AsMemory();

private static readonly ReadOnlyMemory<char> s_zeroes = "0000000000".AsMemory(); // 10 zeros
Copy link
Member

Choose a reason for hiding this comment

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

Why we do we need so many zeros? We only ever need up to three, right?

Copy link
Member Author

@DustinCampbell DustinCampbell Aug 14, 2025

Choose a reason for hiding this comment

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

10 is the largest number of 0s in an int. We don't need the extras for this algorithm, but it seemed reasonable (at the time 😄) to just put the maximum in there.

Copy link
Member Author

Choose a reason for hiding this comment

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

@chsienki: I'll go ahead and merge since you didn't block on this. However, I'm happy to make a change here if you like.

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