Skip to content

Commit

Permalink
Reduce allocations during typing (#75351)
Browse files Browse the repository at this point in the history
* Reduce allocations during typing

During typing, various codepaths attempt to find the set of changes between two text images. Note that while this change will get rid of the allocations, I'm not certain it makes sense for that codepath to be getting executed so frequently and am following up elsewhere.

* Switch to FixedSizeArrayBuilder
  • Loading branch information
ToddGrun authored Oct 3, 2024
1 parent 1c815d7 commit fe0dcf2
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 13 deletions.
9 changes: 5 additions & 4 deletions src/Compilers/Core/Portable/Text/TextChangeRangeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;

namespace Roslyn.Utilities
{
internal static class TextChangeRangeExtensions
{
public static TextChangeRange? Accumulate(this TextChangeRange? accumulatedTextChangeSoFar, IEnumerable<TextChangeRange> changesInNextVersion)
public static TextChangeRange? Accumulate(this TextChangeRange? accumulatedTextChangeSoFar, IReadOnlyList<TextChangeRange> changesInNextVersion)
{
if (!changesInNextVersion.Any())
if (changesInNextVersion.Count == 0)
{
return accumulatedTextChangeSoFar;
}
Expand All @@ -25,7 +24,9 @@ internal static class TextChangeRangeExtensions
// we could apply each one individually like we do in SyntaxDiff::ComputeSpansInNew by calculating delta
// between each change in changesInNextVersion which is already sorted in its textual position ascending order.
// but end result will be same as just applying it once with encompassed text change range.
var newChange = TextChangeRange.Collapse(changesInNextVersion);
var newChange = changesInNextVersion.Count == 1
? changesInNextVersion[0]
: TextChangeRange.Collapse(changesInNextVersion);

// no previous accumulated change, return the new value.
if (accumulatedTextChangeSoFar == null)
Expand Down
38 changes: 29 additions & 9 deletions src/EditorFeatures/Text/ITextImageHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.VisualStudio.Text;
using Roslyn.Utilities;

Expand Down Expand Up @@ -51,35 +52,54 @@ public static IReadOnlyList<TextChangeRange> GetChangeRanges(ITextImageVersion o
}

if (changes == null)
{
return [];
}
else

var builder = new FixedSizeArrayBuilder<TextChangeRange>(changes.Count);
for (var i = 0; i < changes.Count; i++)
{
return ImmutableArray.CreateRange(changes.Select(forward ? s_forwardTextChangeRange : s_backwardTextChangeRange));
var change = changes[i];
builder.Add(forward ? s_forwardTextChangeRange(change) : s_backwardTextChangeRange(change));
}

return builder.MoveToImmutable();
}

private static TextChangeRange GetChangeRanges(ITextImageVersion oldVersion, ITextImageVersion newVersion, bool forward)
{
TextChangeRange? range = null;
var iterator = GetMultipleVersionTextChanges(oldVersion, newVersion, forward);
foreach (var changes in forward ? iterator : iterator.Reverse())
using var _ = ArrayBuilder<ArrayBuilder<TextChangeRange>>.GetInstance(out var builder);

GetMultipleVersionTextChanges(oldVersion, newVersion, forward, builder);
foreach (var changes in builder)
{
range = range.Accumulate(changes);
changes.Free();
}

RoslynDebug.Assert(range.HasValue);
return range.Value;
}

private static IEnumerable<IEnumerable<TextChangeRange>> GetMultipleVersionTextChanges(
ITextImageVersion oldVersion, ITextImageVersion newVersion, bool forward)
private static void GetMultipleVersionTextChanges(
ITextImageVersion oldVersion,
ITextImageVersion newVersion,
bool forward,
ArrayBuilder<ArrayBuilder<TextChangeRange>> builder)
{
for (var version = oldVersion; version != newVersion; version = version.Next)
{
yield return version.Changes.Select(forward ? s_forwardTextChangeRange : s_backwardTextChangeRange);
var changes = ArrayBuilder<TextChangeRange>.GetInstance(version.Changes.Count);
for (var i = 0; i < version.Changes.Count; i++)
{
var change = version.Changes[i];
changes.Add(forward ? s_forwardTextChangeRange(change) : s_backwardTextChangeRange(change));
}

builder.Add(changes);
}

if (!forward)
builder.ReverseContents();
}

private static TextChangeRange CreateTextChangeRange(ITextChange change, bool forward)
Expand Down

0 comments on commit fe0dcf2

Please sign in to comment.