Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Diagnostics
Expand Down Expand Up @@ -208,7 +209,7 @@ private static AnalyzerLoadFailureEventArgs CreateAnalyzerFailedArgs(Exception e
return new AnalyzerLoadFailureEventArgs(errorCode, message, e, typeName);
}

internal ImmutableSortedDictionary<string, ImmutableSortedSet<string>> GetAnalyzerTypeNameMap()
internal ImmutableSortedDictionary<string, ImmutableHashSet<string>> GetAnalyzerTypeNameMap()
{
return _diagnosticAnalyzers.GetExtensionTypeNameMap();
}
Expand All @@ -219,7 +220,7 @@ internal ImmutableSortedDictionary<string, ImmutableSortedSet<string>> GetAnalyz
/// <exception cref="BadImageFormatException">The PE image format is invalid.</exception>
/// <exception cref="IOException">IO error reading the metadata.</exception>
[PerformanceSensitive("https://github.com/dotnet/roslyn/issues/30449")]
private static ImmutableSortedDictionary<string, ImmutableSortedSet<string>> GetAnalyzerTypeNameMap(string fullPath, Type attributeType, AttributeLanguagesFunc languagesFunc)
private static ImmutableSortedDictionary<string, ImmutableHashSet<string>> GetAnalyzerTypeNameMap(string fullPath, Type attributeType, AttributeLanguagesFunc languagesFunc)
{
using var assembly = AssemblyMetadata.CreateFromFile(fullPath);

Expand All @@ -235,7 +236,7 @@ where supportedLanguages.Any()
from supportedLanguage in supportedLanguages
group typeName by supportedLanguage;

return typeNameMap.ToImmutableSortedDictionary(g => g.Key, g => g.ToImmutableSortedSet(StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase);
return typeNameMap.ToImmutableSortedDictionary(g => g.Key, g => g.ToImmutableHashSet(), StringComparer.OrdinalIgnoreCase);
}

private static IEnumerable<string> GetSupportedLanguages(TypeDefinition typeDef, PEModule peModule, Type attributeType, AttributeLanguagesFunc languagesFunc)
Expand Down Expand Up @@ -357,7 +358,7 @@ private sealed class Extensions<TExtension>
private readonly Func<object?, TExtension?>? _coerceFunction;
private ImmutableArray<TExtension> _lazyAllExtensions;
private ImmutableDictionary<string, ImmutableArray<TExtension>> _lazyExtensionsPerLanguage;
private ImmutableSortedDictionary<string, ImmutableSortedSet<string>>? _lazyExtensionTypeNameMap;
private ImmutableSortedDictionary<string, ImmutableHashSet<string>>? _lazyExtensionTypeNameMap;

internal Extensions(AnalyzerFileReference reference, Type attributeType, AttributeLanguagesFunc languagesFunc, bool allowNetFramework, Func<object?, TExtension?>? coerceFunction = null)
{
Expand Down Expand Up @@ -432,7 +433,7 @@ private static ImmutableArray<TExtension> CreateLanguageSpecificExtensions(strin
return builder.ToImmutable();
}

internal ImmutableSortedDictionary<string, ImmutableSortedSet<string>> GetExtensionTypeNameMap()
internal ImmutableSortedDictionary<string, ImmutableHashSet<string>> GetExtensionTypeNameMap()
{
if (_lazyExtensionTypeNameMap == null)
{
Expand All @@ -445,7 +446,7 @@ internal ImmutableSortedDictionary<string, ImmutableSortedSet<string>> GetExtens

internal void AddExtensions(ImmutableSortedDictionary<string, ImmutableArray<TExtension>>.Builder builder)
{
ImmutableSortedDictionary<string, ImmutableSortedSet<string>> analyzerTypeNameMap;
ImmutableSortedDictionary<string, ImmutableHashSet<string>> analyzerTypeNameMap;
Assembly analyzerAssembly;

try
Expand Down Expand Up @@ -493,7 +494,7 @@ internal void AddExtensions(ImmutableSortedDictionary<string, ImmutableArray<TEx

internal void AddExtensions(ImmutableArray<TExtension>.Builder builder, string language, Func<TExtension, bool>? shouldInclude = null)
{
ImmutableSortedDictionary<string, ImmutableSortedSet<string>> analyzerTypeNameMap;
ImmutableSortedDictionary<string, ImmutableHashSet<string>> analyzerTypeNameMap;
Assembly analyzerAssembly;

try
Expand Down Expand Up @@ -557,22 +558,24 @@ bool CheckAssemblyReferencesNewerCompiler(Assembly analyzerAssembly)
return false;
}

private ImmutableArray<TExtension> GetLanguageSpecificAnalyzers(Assembly analyzerAssembly, ImmutableSortedDictionary<string, ImmutableSortedSet<string>> analyzerTypeNameMap, string language, ref bool reportedError)
private ImmutableArray<TExtension> GetLanguageSpecificAnalyzers(Assembly analyzerAssembly, ImmutableSortedDictionary<string, ImmutableHashSet<string>> analyzerTypeNameMap, string language, ref bool reportedError)
{
ImmutableSortedSet<string>? languageSpecificAnalyzerTypeNames;
ImmutableHashSet<string>? languageSpecificAnalyzerTypeNames;
if (!analyzerTypeNameMap.TryGetValue(language, out languageSpecificAnalyzerTypeNames))
{
return ImmutableArray<TExtension>.Empty;
}
return this.GetAnalyzersForTypeNames(analyzerAssembly, languageSpecificAnalyzerTypeNames, ref reportedError);
}

private ImmutableArray<TExtension> GetAnalyzersForTypeNames(Assembly analyzerAssembly, IEnumerable<string> analyzerTypeNames, ref bool reportedError)
private ImmutableArray<TExtension> GetAnalyzersForTypeNames(Assembly analyzerAssembly, ImmutableHashSet<string> analyzerTypeNames, ref bool reportedError)
{
var analyzers = ImmutableArray.CreateBuilder<TExtension>();
var builder = ArrayBuilder<(string typeName, TExtension analyzer)>.GetInstance();

// Given the type names, get the actual System.Type and try to create an instance of the type through reflection.
foreach (var typeName in analyzerTypeNames)
// Randomize the order we instantiate analyzers to avoid static constructor/JIT contention, but still return
// the list of analyzers in the order of the sorted type names for deterministic purpose.
foreach (var typeName in shuffle(analyzerTypeNames))
{
Type? type;
try
Expand Down Expand Up @@ -617,11 +620,36 @@ private ImmutableArray<TExtension> GetAnalyzersForTypeNames(Assembly analyzerAss
TExtension? analyzer = typeInstance as TExtension ?? _coerceFunction?.Invoke(typeInstance);
if (analyzer != null)
{
analyzers.Add(analyzer);
builder.Add((typeName, analyzer));
}
}

return analyzers.ToImmutable();
builder.Sort(static (x, y) => string.Compare(x.typeName, y.typeName, StringComparison.OrdinalIgnoreCase));
var analyzers = builder.SelectAsArray(x => x.analyzer);
builder.Free();

return analyzers;

static IEnumerable<string> shuffle(ImmutableHashSet<string> source)
{
var random =
#if NET6_0_OR_GREATER
Random.Shared;
#else
new Random();
#endif
var builder = ArrayBuilder<string>.GetInstance(source.Count);
builder.AddRange(source);

for (var i = builder.Count - 1; i >= 0; i--)
{
var swapIndex = random.Next(i + 1);
yield return builder[swapIndex];
builder[swapIndex] = builder[i];
}

builder.Free();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Diagnostics
Expand Down Expand Up @@ -220,7 +221,8 @@ private static ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>> C
{
var builder = ImmutableDictionary.CreateBuilder<object, ImmutableArray<DiagnosticAnalyzer>>();

foreach (var reference in analyzerReferencesMap)
// Randomize the order we process analyzer references to minimize static constructor/JIT contention during analyzer instantiation.
foreach (var reference in Shuffle(analyzerReferencesMap))
{
var analyzers = language == null ? reference.Value.GetAnalyzersForAllLanguages() : reference.Value.GetAnalyzers(language);
if (analyzers.Length == 0)
Expand All @@ -233,6 +235,26 @@ private static ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>> C
}

return builder.ToImmutable();

static IEnumerable<KeyValuePair<object, AnalyzerReference>> Shuffle(IDictionary<object, AnalyzerReference> source)
{
var random =
#if NET6_0_OR_GREATER
Random.Shared;
#else
new Random();
#endif

using var _ = ArrayBuilder<KeyValuePair<object, AnalyzerReference>>.GetInstance(source.Count, out var builder);
builder.AddRange(source);

for (var i = builder.Count - 1; i >= 0; i--)
{
var swapIndex = random.Next(i + 1);
yield return builder[swapIndex];
builder[swapIndex] = builder[i];
}
}
}

private static ImmutableDictionary<object, AnalyzerReference> CreateAnalyzerReferencesMap(IEnumerable<AnalyzerReference> analyzerReferences)
Expand Down