Skip to content

Commit

Permalink
Reduce allocations in AbstractSymbolCompletionProvider.CreateItems (#…
Browse files Browse the repository at this point in the history
…74670)

* Reduce allocations in AbstractSymbolCompletionProvider.CreateItems

The linq expression shows up as about 0.3% of allocations in the profile I'm looking for in the typing section of the scrolling speedometer test.

MultiDictionary has a struct-based value that is great for the case where the valueset typically is only a single object (which is the case for this code).
  • Loading branch information
ToddGrun authored Aug 8, 2024
1 parent d1fe9e9 commit 9682b84
Showing 1 changed file with 37 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -108,20 +108,36 @@ private ImmutableArray<CompletionItem> CreateItems(
// We might get symbol w/o name but CanBeReferencedByName is still set to true,
// need to filter them out.
// https://github.com/dotnet/roslyn/issues/47690
var symbolGroups = from symbol in symbols
let texts = GetDisplayAndSuffixAndInsertionText(symbol.Symbol, contextLookup(symbol))
where !string.IsNullOrWhiteSpace(texts.displayText)
group symbol by texts into g
select g;
//
// Use SymbolReferenceEquivalenceComparer.Instance as the value comparer as we
// don't want symbols with just the same name to necessarily match
// (as the default comparer on SymbolAndSelectionInfo does)
var symbolGroups = new MultiDictionary<(string displayText, string suffix, string insertionText), SymbolAndSelectionInfo>(
capacity: symbols.Length,
comparer: EqualityComparer<(string, string, string)>.Default,
valueComparer: SymbolReferenceEquivalenceComparer.Instance);

foreach (var symbol in symbols)
{
var texts = GetDisplayAndSuffixAndInsertionText(symbol.Symbol, contextLookup(symbol));
if (!string.IsNullOrWhiteSpace(texts.displayText))
{
symbolGroups.Add(texts, symbol);
}
}

using var _ = ArrayBuilder<CompletionItem>.GetInstance(out var itemListBuilder);
var typeConvertibilityCache = new Dictionary<ITypeSymbol, bool>(SymbolEqualityComparer.Default);

foreach (var symbolGroup in symbolGroups)
{
var includeItemInTargetTypedCompletion = false;
var arbitraryFirstContext = contextLookup(symbolGroup.First());
var symbolList = symbolGroup.ToImmutableArray();
using var symbolListBuilder = TemporaryArray<SymbolAndSelectionInfo>.Empty;
foreach (var symbol in symbolGroup.Value)
symbolListBuilder.Add(symbol);

var symbolList = symbolListBuilder.ToImmutableAndClear();
var arbitraryFirstContext = contextLookup(symbolList[0]);

if (completionContext.CompletionOptions.TargetTypedCompletionFilter)
{
Expand Down Expand Up @@ -151,6 +167,20 @@ group symbol by texts into g
return itemListBuilder.ToImmutableAndClear();
}

/// <summary>
/// Alternative comparer to SymbolAndSelectionInfo's default which considers both the full symbol and preselect.
/// </summary>
private sealed class SymbolReferenceEquivalenceComparer : IEqualityComparer<SymbolAndSelectionInfo>
{
public static readonly SymbolReferenceEquivalenceComparer Instance = new();

public bool Equals(SymbolAndSelectionInfo x, SymbolAndSelectionInfo y)
=> x.Symbol == y.Symbol && x.Preselect == y.Preselect;

public int GetHashCode(SymbolAndSelectionInfo symbol)
=> Hash.Combine(symbol.Symbol.GetHashCode(), symbol.Preselect ? 1 : 0);
}

protected static bool TryFindFirstSymbolMatchesTargetTypes(
Func<SymbolAndSelectionInfo, TSyntaxContext> contextLookup,
ImmutableArray<SymbolAndSelectionInfo> symbolList,
Expand Down

0 comments on commit 9682b84

Please sign in to comment.