Skip to content

Commit fd01491

Browse files
authored
Merge pull request #69838 from genlu/RandomAnalyzer
Randomize analyzer instantiation to avoid static constructor/jit contention
2 parents 3f998d7 + e6a56b3 commit fd01491

File tree

2 files changed

+65
-15
lines changed

2 files changed

+65
-15
lines changed

src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerFileReference.cs

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using System.Runtime.CompilerServices;
1616
using System.Runtime.Versioning;
1717
using System.Threading;
18+
using Microsoft.CodeAnalysis.PooledObjects;
1819
using Roslyn.Utilities;
1920

2021
namespace Microsoft.CodeAnalysis.Diagnostics
@@ -208,7 +209,7 @@ private static AnalyzerLoadFailureEventArgs CreateAnalyzerFailedArgs(Exception e
208209
return new AnalyzerLoadFailureEventArgs(errorCode, message, e, typeName);
209210
}
210211

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

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

238-
return typeNameMap.ToImmutableSortedDictionary(g => g.Key, g => g.ToImmutableSortedSet(StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase);
239+
return typeNameMap.ToImmutableSortedDictionary(g => g.Key, g => g.ToImmutableHashSet(), StringComparer.OrdinalIgnoreCase);
239240
}
240241

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

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

435-
internal ImmutableSortedDictionary<string, ImmutableSortedSet<string>> GetExtensionTypeNameMap()
436+
internal ImmutableSortedDictionary<string, ImmutableHashSet<string>> GetExtensionTypeNameMap()
436437
{
437438
if (_lazyExtensionTypeNameMap == null)
438439
{
@@ -445,7 +446,7 @@ internal ImmutableSortedDictionary<string, ImmutableSortedSet<string>> GetExtens
445446

446447
internal void AddExtensions(ImmutableSortedDictionary<string, ImmutableArray<TExtension>>.Builder builder)
447448
{
448-
ImmutableSortedDictionary<string, ImmutableSortedSet<string>> analyzerTypeNameMap;
449+
ImmutableSortedDictionary<string, ImmutableHashSet<string>> analyzerTypeNameMap;
449450
Assembly analyzerAssembly;
450451

451452
try
@@ -493,7 +494,7 @@ internal void AddExtensions(ImmutableSortedDictionary<string, ImmutableArray<TEx
493494

494495
internal void AddExtensions(ImmutableArray<TExtension>.Builder builder, string language, Func<TExtension, bool>? shouldInclude = null)
495496
{
496-
ImmutableSortedDictionary<string, ImmutableSortedSet<string>> analyzerTypeNameMap;
497+
ImmutableSortedDictionary<string, ImmutableHashSet<string>> analyzerTypeNameMap;
497498
Assembly analyzerAssembly;
498499

499500
try
@@ -557,22 +558,24 @@ bool CheckAssemblyReferencesNewerCompiler(Assembly analyzerAssembly)
557558
return false;
558559
}
559560

560-
private ImmutableArray<TExtension> GetLanguageSpecificAnalyzers(Assembly analyzerAssembly, ImmutableSortedDictionary<string, ImmutableSortedSet<string>> analyzerTypeNameMap, string language, ref bool reportedError)
561+
private ImmutableArray<TExtension> GetLanguageSpecificAnalyzers(Assembly analyzerAssembly, ImmutableSortedDictionary<string, ImmutableHashSet<string>> analyzerTypeNameMap, string language, ref bool reportedError)
561562
{
562-
ImmutableSortedSet<string>? languageSpecificAnalyzerTypeNames;
563+
ImmutableHashSet<string>? languageSpecificAnalyzerTypeNames;
563564
if (!analyzerTypeNameMap.TryGetValue(language, out languageSpecificAnalyzerTypeNames))
564565
{
565566
return ImmutableArray<TExtension>.Empty;
566567
}
567568
return this.GetAnalyzersForTypeNames(analyzerAssembly, languageSpecificAnalyzerTypeNames, ref reportedError);
568569
}
569570

570-
private ImmutableArray<TExtension> GetAnalyzersForTypeNames(Assembly analyzerAssembly, IEnumerable<string> analyzerTypeNames, ref bool reportedError)
571+
private ImmutableArray<TExtension> GetAnalyzersForTypeNames(Assembly analyzerAssembly, ImmutableHashSet<string> analyzerTypeNames, ref bool reportedError)
571572
{
572-
var analyzers = ImmutableArray.CreateBuilder<TExtension>();
573+
var builder = ArrayBuilder<(string typeName, TExtension analyzer)>.GetInstance();
573574

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

624-
return analyzers.ToImmutable();
627+
builder.Sort(static (x, y) => string.Compare(x.typeName, y.typeName, StringComparison.OrdinalIgnoreCase));
628+
var analyzers = builder.SelectAsArray(x => x.analyzer);
629+
builder.Free();
630+
631+
return analyzers;
632+
633+
static IEnumerable<string> shuffle(ImmutableHashSet<string> source)
634+
{
635+
var random =
636+
#if NET6_0_OR_GREATER
637+
Random.Shared;
638+
#else
639+
new Random();
640+
#endif
641+
var builder = ArrayBuilder<string>.GetInstance(source.Count);
642+
builder.AddRange(source);
643+
644+
for (var i = builder.Count - 1; i >= 0; i--)
645+
{
646+
var swapIndex = random.Next(i + 1);
647+
yield return builder[swapIndex];
648+
builder[swapIndex] = builder[i];
649+
}
650+
651+
builder.Free();
652+
}
625653
}
626654
}
627655

src/Workspaces/Core/Portable/Diagnostics/HostDiagnosticAnalyzers.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Collections.Immutable;
99
using System.Linq;
1010
using System.Runtime.CompilerServices;
11+
using Microsoft.CodeAnalysis.PooledObjects;
1112
using Roslyn.Utilities;
1213

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

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

235237
return builder.ToImmutable();
238+
239+
static IEnumerable<KeyValuePair<object, AnalyzerReference>> Shuffle(IDictionary<object, AnalyzerReference> source)
240+
{
241+
var random =
242+
#if NET6_0_OR_GREATER
243+
Random.Shared;
244+
#else
245+
new Random();
246+
#endif
247+
248+
using var _ = ArrayBuilder<KeyValuePair<object, AnalyzerReference>>.GetInstance(source.Count, out var builder);
249+
builder.AddRange(source);
250+
251+
for (var i = builder.Count - 1; i >= 0; i--)
252+
{
253+
var swapIndex = random.Next(i + 1);
254+
yield return builder[swapIndex];
255+
builder[swapIndex] = builder[i];
256+
}
257+
}
236258
}
237259

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

0 commit comments

Comments
 (0)