Skip to content

Commit

Permalink
Reduce allocations in AbstractProjectExtensionProvider.FilterExtensio…
Browse files Browse the repository at this point in the history
…ns (#74112)

* Reduce allocations in AbstractProjectExtensionProvider.FilterExtensions

This ends up reducing allocations in the profile where I repeatedly brough up a lightbulb by about 0.7%.

1) Got rid of an Enum.ToString that happened on each filtering. Instead changed the code that reads the mef attributes to convert from string -> enum.
2) Got rid of a couple Contains calls which were allocating an enumerator
3) Got rid of some closures by using the args version of WhereAsArray and by creating a separate method to handle the CWT insertion.
  • Loading branch information
ToddGrun authored Jun 26, 2024
1 parent 01bb220 commit 6a1c377
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ static ImmutableArray<CodeRefactoringProvider> GetProjectRefactorings(TextDocume
}

static ProjectCodeRefactoringProvider.ExtensionInfo GetExtensionInfo(ExportCodeRefactoringProviderAttribute attribute)
=> new(attribute.DocumentKinds, attribute.DocumentExtensions);
{
var kinds = EnumArrayConverter.FromStringArray<TextDocumentKind>(attribute.DocumentKinds);

return new(kinds, attribute.DocumentExtensions);
}
}

public async Task<bool> HasRefactoringsAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal abstract class AbstractProjectExtensionProvider<TProvider, TExtension,
where TExportAttribute : Attribute
where TExtension : class
{
public record class ExtensionInfo(string[] DocumentKinds, string[]? DocumentExtensions);
public record class ExtensionInfo(ImmutableArray<TextDocumentKind> DocumentKinds, string[]? DocumentExtensions);

// Following CWTs are used to cache completion providers from projects' references,
// so we can avoid the slow path unless there's any change to the references.
Expand Down Expand Up @@ -87,48 +87,52 @@ public static ImmutableArray<TExtension> GetExtensions(TextDocument document, Fu

public static ImmutableArray<TExtension> FilterExtensions(TextDocument document, ImmutableArray<TExtension> extensions, Func<TExportAttribute, ExtensionInfo> getExtensionInfoForFiltering)
{
return extensions.WhereAsArray(ShouldIncludeExtension);
return extensions.WhereAsArray(ShouldIncludeExtension, (document, getExtensionInfoForFiltering));

bool ShouldIncludeExtension(TExtension extension)
static bool ShouldIncludeExtension(TExtension extension, (TextDocument, Func<TExportAttribute, ExtensionInfo>) args)
{
var (document, getExtensionInfoForFiltering) = args;
if (!s_extensionInfoMap.TryGetValue(extension, out var extensionInfo))
{
extensionInfo = s_extensionInfoMap.GetValue(extension,
new ConditionalWeakTable<TExtension, ExtensionInfo?>.CreateValueCallback(ComputeExtensionInfo));
}
extensionInfo = GetOrCreateExtensionInfo(extension, getExtensionInfoForFiltering);

if (extensionInfo == null)
return true;

if (!extensionInfo.DocumentKinds.Contains(document.Kind.ToString()))
if (extensionInfo.DocumentKinds.IndexOf(document.Kind) < 0)
return false;

if (document.FilePath != null &&
extensionInfo.DocumentExtensions != null &&
!extensionInfo.DocumentExtensions.Contains(PathUtilities.GetExtension(document.FilePath)))
Array.IndexOf(extensionInfo.DocumentExtensions, PathUtilities.GetExtension(document.FilePath)) < 0)
{
return false;
}

return true;
}

ExtensionInfo? ComputeExtensionInfo(TExtension extension)
static ExtensionInfo? GetOrCreateExtensionInfo(TExtension extension, Func<TExportAttribute, ExtensionInfo> getExtensionInfoForFiltering)
{
TExportAttribute? attribute;
try
{
var typeInfo = extension.GetType().GetTypeInfo();
attribute = typeInfo.GetCustomAttribute<TExportAttribute>();
}
catch
return s_extensionInfoMap.GetValue(extension,
new ConditionalWeakTable<TExtension, ExtensionInfo?>.CreateValueCallback(ComputeExtensionInfo));

ExtensionInfo? ComputeExtensionInfo(TExtension extension)
{
attribute = null;
}
TExportAttribute? attribute;
try
{
var typeInfo = extension.GetType().GetTypeInfo();
attribute = typeInfo.GetCustomAttribute<TExportAttribute>();
}
catch
{
attribute = null;
}

if (attribute == null)
return null;
return getExtensionInfoForFiltering(attribute);
if (attribute == null)
return null;
return getExtensionInfoForFiltering(attribute);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,11 @@ private ImmutableDictionary<DiagnosticId, ImmutableArray<CodeFixProvider>> Compu
}

private static ProjectCodeFixProvider.ExtensionInfo GetExtensionInfo(ExportCodeFixProviderAttribute attribute)
=> new(attribute.DocumentKinds, attribute.DocumentExtensions);
{
var kinds = EnumArrayConverter.FromStringArray<TextDocumentKind>(attribute.DocumentKinds);

return new(kinds, attribute.DocumentExtensions);
}

private sealed class FixerComparer : IComparer<CodeFixProvider>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Immutable;

namespace Microsoft.CodeAnalysis.CodeFixesAndRefactorings;

internal static class EnumArrayConverter
{
public static ImmutableArray<TEnum> FromStringArray<TEnum>(string[] strings) where TEnum : struct, Enum
{
var enums = new FixedSizeArrayBuilder<TEnum>(strings.Length);
for (var i = 0; i < strings.Length; i++)
{
var s = strings[i];
if (!Enum.TryParse(s, out TEnum enumValue))
enumValue = default;

enums.Add(enumValue);
}

return enums.MoveToImmutable();
}
}

0 comments on commit 6a1c377

Please sign in to comment.