Skip to content

Commit a11eba3

Browse files
authored
Reduce allocations during completion in FilterToVisibleAndBrowsableSymbols (#77315)
* Reduce allocations during completion in FilterToVisibleAndBrowsableSymbols This method was visible in allocations in one of the completion speedometer profiles I was looking at and has a couple of easily removable allocations. Can combine two WhereAsArray calls Can switch to using WhereAsArray(ImmutableAray) instead of the IEnumerable overload Can use the pooling support in MetadataUnifyingSymbolHashSet Can avoid EditorBrowsableInfo allocation when input symbols is empty Can avoid the closure allocation that was present in RemoveOverriddenSymbolsWithinSet Even with all these changes, the net benefit didn't prove large from the speedometer run, but I've done the work, it doesn't complicate things, and every bit helps.
1 parent db38112 commit a11eba3

File tree

4 files changed

+33
-26
lines changed

4 files changed

+33
-26
lines changed

src/Workspaces/Core/Portable/FindSymbols/FindReferences/MetadataUnifyingSymbolHashSet.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,32 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using Microsoft.CodeAnalysis.PooledObjects;
78

89
namespace Microsoft.CodeAnalysis.FindSymbols;
910

10-
internal sealed class MetadataUnifyingSymbolHashSet : HashSet<ISymbol>
11+
internal sealed class MetadataUnifyingSymbolHashSet : HashSet<ISymbol>, IPooled
1112
{
1213
private static readonly ObjectPool<MetadataUnifyingSymbolHashSet> s_metadataUnifyingSymbolHashSetPool = new(() => []);
1314

1415
public MetadataUnifyingSymbolHashSet() : base(MetadataUnifyingEquivalenceComparer.Instance)
1516
{
1617
}
1718

18-
public static MetadataUnifyingSymbolHashSet AllocateFromPool()
19-
=> s_metadataUnifyingSymbolHashSetPool.Allocate();
19+
public static PooledDisposer<MetadataUnifyingSymbolHashSet> GetInstance(out MetadataUnifyingSymbolHashSet instance)
20+
{
21+
instance = s_metadataUnifyingSymbolHashSetPool.Allocate();
22+
Debug.Assert(instance.Count == 0);
23+
24+
return new PooledDisposer<MetadataUnifyingSymbolHashSet>(instance);
25+
}
2026

21-
public static void ClearAndFree(MetadataUnifyingSymbolHashSet set)
27+
public void Free(bool discardLargeInstances)
2228
{
23-
set.Clear();
24-
s_metadataUnifyingSymbolHashSetPool.Free(set);
29+
// ignore discardLargeInstances as we don't limit our pooled hashset capacities
30+
Clear();
31+
32+
s_metadataUnifyingSymbolHashSetPool.Free(this);
2533
}
2634
}

src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -645,38 +645,37 @@ private static bool ElementNameIs(XElement element, string name)
645645
public static ImmutableArray<T> FilterToVisibleAndBrowsableSymbols<T>(
646646
this ImmutableArray<T> symbols, bool hideAdvancedMembers, Compilation compilation) where T : ISymbol
647647
{
648-
symbols = symbols.RemoveOverriddenSymbolsWithinSet();
648+
if (symbols.Length == 0)
649+
return [];
650+
651+
using var _ = MetadataUnifyingSymbolHashSet.GetInstance(out var overriddenSymbols);
652+
653+
foreach (var symbol in symbols)
654+
{
655+
var overriddenMember = symbol.GetOverriddenMember();
656+
if (overriddenMember != null)
657+
overriddenSymbols.Add(overriddenMember);
658+
}
649659

650660
// Since all symbols are from the same compilation, find the required attribute
651661
// constructors once and reuse.
652662
var editorBrowsableInfo = new EditorBrowsableInfo(compilation);
653663

654664
// PERF: HasUnsupportedMetadata may require recreating the syntax tree to get the base class, so first
655665
// check to see if we're referencing a symbol defined in source.
656-
return symbols.WhereAsArray((s, arg) =>
666+
var filteredSymbols = symbols.WhereAsArray(static (s, arg) =>
657667
// Check if symbol is namespace (which is always visible) first to avoid realizing all locations
658668
// of each namespace symbol, which might end up allocating in LOH
669+
!arg.overriddenSymbols.Contains(s) &&
659670
(s is INamespaceSymbol || s.Locations.Any(static loc => loc.IsInSource) || !s.HasUnsupportedMetadata) &&
660671
!s.IsDestructor() &&
661672
s.IsEditorBrowsable(
662673
arg.hideAdvancedMembers,
663674
arg.editorBrowsableInfo.Compilation,
664675
arg.editorBrowsableInfo),
665-
(hideAdvancedMembers, editorBrowsableInfo));
666-
}
667-
668-
private static ImmutableArray<T> RemoveOverriddenSymbolsWithinSet<T>(this ImmutableArray<T> symbols) where T : ISymbol
669-
{
670-
var overriddenSymbols = new MetadataUnifyingSymbolHashSet();
671-
672-
foreach (var symbol in symbols)
673-
{
674-
var overriddenMember = symbol.GetOverriddenMember();
675-
if (overriddenMember != null && !overriddenSymbols.Contains(overriddenMember))
676-
overriddenSymbols.Add(overriddenMember);
677-
}
676+
arg: (hideAdvancedMembers, editorBrowsableInfo, overriddenSymbols));
678677

679-
return symbols.WhereAsArray(s => !overriddenSymbols.Contains(s));
678+
return filteredSymbols;
680679
}
681680

682681
public static ImmutableArray<T> FilterToVisibleAndBrowsableSymbolsAndNotUnsafeSymbols<T>(

src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/IMethodSymbolExtensions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,13 @@ public static IMethodSymbol RemoveInaccessibleAttributesAndAttributesOfTypes(
163163
method,
164164
containingType: method.ContainingType,
165165
explicitInterfaceImplementations: method.ExplicitInterfaceImplementations,
166-
attributes: method.GetAttributes().WhereAsArray(static (a, arg) => !shouldRemoveAttribute(a, arg), arg),
166+
attributes: method.GetAttributes().WhereAsArray(static (a, arg) => !shouldRemoveAttribute(a, arg), arg: arg),
167167
parameters: method.Parameters.SelectAsArray(static (p, arg) =>
168168
CodeGenerationSymbolFactory.CreateParameterSymbol(
169-
p.GetAttributes().WhereAsArray(static (a, arg) => !shouldRemoveAttribute(a, arg), arg),
169+
p.GetAttributes().WhereAsArray(static (a, arg) => !shouldRemoveAttribute(a, arg), arg: arg),
170170
p.RefKind, p.IsParams, p.Type, p.Name, p.IsOptional,
171171
p.HasExplicitDefaultValue, p.HasExplicitDefaultValue ? p.ExplicitDefaultValue : null), arg),
172-
returnTypeAttributes: method.GetReturnTypeAttributes().WhereAsArray(static (a, arg) => !shouldRemoveAttribute(a, arg), arg));
172+
returnTypeAttributes: method.GetReturnTypeAttributes().WhereAsArray(static (a, arg) => !shouldRemoveAttribute(a, arg), arg: arg));
173173

174174
static bool shouldRemoveAttribute(AttributeData a, (INamedTypeSymbol[] removeAttributeTypes, ISymbol accessibleWithin) arg)
175175
=> arg.removeAttributeTypes.Any(attr => attr.Equals(a.AttributeClass)) ||

src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/IPropertySymbolExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public static IPropertySymbol RemoveInaccessibleAttributesAndAttributesOfTypes(
5959
property.Name,
6060
property.Parameters.SelectAsArray(static (p, arg) =>
6161
CodeGenerationSymbolFactory.CreateParameterSymbol(
62-
p.GetAttributes().WhereAsArray(static (a, arg) => !ShouldRemoveAttribute(a, arg), arg),
62+
p.GetAttributes().WhereAsArray(static (a, arg) => !ShouldRemoveAttribute(a, arg), arg: arg),
6363
p.RefKind, p.IsParams, p.Type, p.Name, p.IsOptional,
6464
p.HasExplicitDefaultValue, p.HasExplicitDefaultValue ? p.ExplicitDefaultValue : null), arg),
6565
property.GetMethod,

0 commit comments

Comments
 (0)