Skip to content

Reduce TextDiffer allocations, including LOH#11983

Merged
ToddGrun merged 2 commits intodotnet:mainfrom
ToddGrun:dev/toddgrun/TextDifferAllocations
Jun 27, 2025
Merged

Reduce TextDiffer allocations, including LOH#11983
ToddGrun merged 2 commits intodotnet:mainfrom
ToddGrun:dev/toddgrun/TextDifferAllocations

Conversation

@ToddGrun
Copy link
Contributor

TextDiffer.ComputeDiff shows as 1.3% of allocations in the razor speedometer scrolling and typing test. There are two issues:

  1. The IntArray class was using too large a page size when used in conjunction with System.Buffers.ArrayPool. It was indicating it wanted an 80KB buffer, and ArrayPool allocates it's pool entries on powers of 2, so it was giving back an 128KB array, which goes to the LOH. The fix is to request a 64 KB buffer.

  2. The IntArrays were being constructed over unnecessarilly large ranges. If we instead prime the pump by detemining an initial changed extent, we can request significantly less allocated space during the typing scenario (reduces the requested array size from the buffer size to just the changed range, tyipcally only a couple characters)

Reducing the size of the vf/vr arrays did require a redirection to the SourceEqual method in the derived classes, as they don't know of that concept.

image

TextDiffer.ComputeDiff shows as 1.3% of allocations in the razor speedometer scrolling and typing test. There are two issues:

1) The IntArray class was using too large a page size when used in conjunction with System.Buffers.ArrayPool. It was indicating it wanted an 80KB buffer, and ArrayPool allocates it's pool entries on powers of 2, so it was giving back an 128KB array, which goes to the LOH. The fix is to request a 64 KB buffer.

2) The IntArrays were being constructed over unnecessarilly large ranges. If we instead prime the pump by detemining an initial changed extent, we can request significantly less allocated space during the typing scenario (reduces the requested array size from the buffer size to just the changed range, tyipcally only a couple characters)

Reducing the size of the vf/vr arrays did require a redirection to the SourceEqual method in the derived classes, as they don't know of that concept.
@ToddGrun ToddGrun requested a review from a team as a code owner June 26, 2025 20:49
using var vr = new IntArray((2 * max) + 1);

ComputeDiffRecursive(builder, 0, OldSourceLength, 0, NewSourceLength, vf, vr);
ComputeDiffRecursive(builder, 0, oldSourceLength, 0, newSourceLength, vf, vr);
Copy link
Member

Choose a reason for hiding this comment

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

I suspect the answer is buried in the complicated algorithm I'm refusing to look at, but why pass 0 here only to then apply an offset later, instead of just computing the diff starting from the offset?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried using an offset to index into the vf/vr vectors in, but that got logically quite hairy, and I still had bugs after trying that for a couple hours. It seemed easier to just say:

algorithm, do your thing over this smaller region. But, when you need to call SourceEqual, let me intersept that call and do the translation. And when you are done, I'll translate your answer to use the appropriate coordinate space.

Copy link
Member

Choose a reason for hiding this comment

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

I would have done the same. I hate these highly mathematical algorithms with tiny variable names. I didn't get into this business to do math.

@ToddGrun ToddGrun merged commit 08529a5 into dotnet:main Jun 27, 2025
11 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Jun 27, 2025
{
var edit = edits[i];

edits[i] = new DiffEdit(edit.Kind, _oldSourceOffset + edit.Position, _newSourceOffset + edit.NewTextPosition, edit.Length);
Copy link
Member

Choose a reason for hiding this comment

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

Since there was effort to make the DiffEdit constructor private and provide factory methods, I'd prefer an Offset method that takes adjustments to DiffEdit.Position and DiffEdit.NewTextPosition rather than exposing the constructor and passing half of the same data back in.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ToddGrun added a commit to ToddGrun/razor that referenced this pull request Jun 27, 2025
Specifically, provide a factory method for offseting a DiffEdit instead of exposing the DiffEdit ctor
ToddGrun added a commit that referenced this pull request Jun 27, 2025
Specifically, provide a factory method for offseting a DiffEdit instead of exposing the DiffEdit ctor
@RikkiGibson RikkiGibson modified the milestones: Next, 18.0 P1 Aug 20, 2025
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