提交 af4b371a 编写于 作者: M Manish Vasani

Merge pull request #8461 from mavasani/AnalyzerManagerPerf

Address performance issues in the AnalyzerManager reported at…
...@@ -32,21 +32,49 @@ internal partial class AnalyzerManager ...@@ -32,21 +32,49 @@ internal partial class AnalyzerManager
// This map stores the tasks to compute HostSessionStartAnalysisScope for session wide analyzer actions, i.e. AnalyzerActions registered by analyzer's Initialize method. // This map stores the tasks to compute HostSessionStartAnalysisScope for session wide analyzer actions, i.e. AnalyzerActions registered by analyzer's Initialize method.
// These are run only once per every analyzer. // These are run only once per every analyzer.
private readonly ConcurrentDictionary<DiagnosticAnalyzer, Task<HostSessionStartAnalysisScope>> _sessionScopeMap = private readonly Dictionary<DiagnosticAnalyzer, Task<HostSessionStartAnalysisScope>> _sessionScopeMap =
new ConcurrentDictionary<DiagnosticAnalyzer, Task<HostSessionStartAnalysisScope>>(concurrencyLevel: 2, capacity: 5); new Dictionary<DiagnosticAnalyzer, Task<HostSessionStartAnalysisScope>>(capacity: 5);
// This map stores the tasks to compute HostCompilationStartAnalysisScope for per-compilation analyzer actions, i.e. AnalyzerActions registered by analyzer's CompilationStartActions. // This map stores the tasks to compute HostCompilationStartAnalysisScope for per-compilation analyzer actions, i.e. AnalyzerActions registered by analyzer's CompilationStartActions.
// Compilation start actions will get executed once per-each AnalyzerAndOptions as user might want to return different set of custom actions for each compilation/analyzer options. // Compilation start actions will get executed once per-each AnalyzerAndOptions as user might want to return different set of custom actions for each compilation/analyzer options.
private readonly ConcurrentDictionary<AnalyzerAndOptions, ConditionalWeakTable<Compilation, Task<HostCompilationStartAnalysisScope>>> _compilationScopeMap = private readonly Dictionary<AnalyzerAndOptions, ConditionalWeakTable<Compilation, Task<HostCompilationStartAnalysisScope>>> _compilationScopeMap =
new ConcurrentDictionary<AnalyzerAndOptions, ConditionalWeakTable<Compilation, Task<HostCompilationStartAnalysisScope>>>(concurrencyLevel: 2, capacity: 5); new Dictionary<AnalyzerAndOptions, ConditionalWeakTable<Compilation, Task<HostCompilationStartAnalysisScope>>>(capacity: 5);
/// <summary> /// <summary>
/// Cache descriptors for each diagnostic analyzer. We do this since <see cref="DiagnosticAnalyzer.SupportedDiagnostics"/> is /// Cache descriptors for each diagnostic analyzer. We do this since <see cref="DiagnosticAnalyzer.SupportedDiagnostics"/> is
/// a property rather than metadata. We expect it to be cheap and immutable, but we can't force them to be so, we cache them /// a property rather than metadata. We expect it to be cheap and immutable, but we can't force them to be so, we cache them
/// and ask only once. /// and ask only once.
/// </summary> /// </summary>
private readonly ConcurrentDictionary<DiagnosticAnalyzer, Tuple<ImmutableArray<DiagnosticDescriptor>, EventHandler<Exception>>> _descriptorCache = private readonly Dictionary<DiagnosticAnalyzer, Tuple<ImmutableArray<DiagnosticDescriptor>, EventHandler<Exception>>> _descriptorMap =
new ConcurrentDictionary<DiagnosticAnalyzer, Tuple<ImmutableArray<DiagnosticDescriptor>, EventHandler<Exception>>>(concurrencyLevel: 2, capacity: 5); new Dictionary<DiagnosticAnalyzer, Tuple<ImmutableArray<DiagnosticDescriptor>, EventHandler<Exception>>>(capacity: 5);
private ConditionalWeakTable<Compilation, Task<HostCompilationStartAnalysisScope>> GetOrCreateCompilationActionsCache(AnalyzerAndOptions analyzerAndOptions)
{
lock (_compilationScopeMap)
{
ConditionalWeakTable<Compilation, Task<HostCompilationStartAnalysisScope>> value;
if (_compilationScopeMap.TryGetValue(analyzerAndOptions, out value))
{
return value;
}
value = new ConditionalWeakTable<Compilation, Task<HostCompilationStartAnalysisScope>>();
_compilationScopeMap.Add(analyzerAndOptions, value);
return value;
}
}
private void ClearCompilationScopeMap(AnalyzerAndOptions analyzerAndOptions, AnalyzerExecutor analyzerExecutor)
{
ConditionalWeakTable<Compilation, Task<HostCompilationStartAnalysisScope>> compilationActionsCache;
lock (_compilationScopeMap)
{
if (_compilationScopeMap.TryGetValue(analyzerAndOptions, out compilationActionsCache))
{
compilationActionsCache.Remove(analyzerExecutor.Compilation);
}
}
}
private Task<HostCompilationStartAnalysisScope> GetCompilationAnalysisScopeCoreAsync( private Task<HostCompilationStartAnalysisScope> GetCompilationAnalysisScopeCoreAsync(
AnalyzerAndOptions analyzerAndOptions, AnalyzerAndOptions analyzerAndOptions,
...@@ -64,8 +92,8 @@ internal partial class AnalyzerManager ...@@ -64,8 +92,8 @@ internal partial class AnalyzerManager
}; };
var callback = new ConditionalWeakTable<Compilation, Task<HostCompilationStartAnalysisScope>>.CreateValueCallback(getTask); var callback = new ConditionalWeakTable<Compilation, Task<HostCompilationStartAnalysisScope>>.CreateValueCallback(getTask);
var compilationActionsMap = _compilationScopeMap.GetOrAdd(analyzerAndOptions, new ConditionalWeakTable<Compilation, Task<HostCompilationStartAnalysisScope>>()); var compilationActionsCache = GetOrCreateCompilationActionsCache(analyzerAndOptions);
return compilationActionsMap.GetValue(analyzerExecutor.Compilation, callback); return compilationActionsCache.GetValue(analyzerExecutor.Compilation, callback);
} }
private async Task<HostCompilationStartAnalysisScope> GetCompilationAnalysisScopeAsync( private async Task<HostCompilationStartAnalysisScope> GetCompilationAnalysisScopeAsync(
...@@ -83,32 +111,42 @@ internal partial class AnalyzerManager ...@@ -83,32 +111,42 @@ internal partial class AnalyzerManager
{ {
// Task to compute the scope was cancelled. // Task to compute the scope was cancelled.
// Clear the entry in scope map for analyzer, so we can attempt a retry. // Clear the entry in scope map for analyzer, so we can attempt a retry.
ConditionalWeakTable<Compilation, Task<HostCompilationStartAnalysisScope>> compilationActionsMap; ClearCompilationScopeMap(analyzerAndOptions, analyzerExecutor);
if (_compilationScopeMap.TryGetValue(analyzerAndOptions, out compilationActionsMap))
{
compilationActionsMap.Remove(analyzerExecutor.Compilation);
}
analyzerExecutor.CancellationToken.ThrowIfCancellationRequested(); analyzerExecutor.CancellationToken.ThrowIfCancellationRequested();
return await GetCompilationAnalysisScopeAsync(analyzer, sessionScope, analyzerExecutor).ConfigureAwait(false); return await GetCompilationAnalysisScopeAsync(analyzer, sessionScope, analyzerExecutor).ConfigureAwait(false);
} }
} }
private Task<HostSessionStartAnalysisScope> GetSessionAnalysisScopeCoreAsync( private Task<HostSessionStartAnalysisScope> GetSessionAnalysisScopeTask(
DiagnosticAnalyzer analyzer, DiagnosticAnalyzer analyzer,
AnalyzerExecutor analyzerExecutor) AnalyzerExecutor analyzerExecutor)
{ {
Func<DiagnosticAnalyzer, Task<HostSessionStartAnalysisScope>> getTask = a => lock (_sessionScopeMap)
{ {
return Task.Run(() => return GetSessionAnalysisScopeTask_NoLock(analyzer, analyzerExecutor);
{ }
var sessionScope = new HostSessionStartAnalysisScope(); }
analyzerExecutor.ExecuteInitializeMethod(a, sessionScope);
return sessionScope;
}, analyzerExecutor.CancellationToken);
};
return _sessionScopeMap.GetOrAdd(analyzer, getTask); private Task<HostSessionStartAnalysisScope> GetSessionAnalysisScopeTask_NoLock(
DiagnosticAnalyzer analyzer,
AnalyzerExecutor analyzerExecutor)
{
Task<HostSessionStartAnalysisScope> task;
if (_sessionScopeMap.TryGetValue(analyzer, out task))
{
return task;
}
task = Task.Run(() =>
{
var sessionScope = new HostSessionStartAnalysisScope();
analyzerExecutor.ExecuteInitializeMethod(analyzer, sessionScope);
return sessionScope;
}, analyzerExecutor.CancellationToken);
_sessionScopeMap.Add(analyzer, task);
return task;
} }
private async Task<HostSessionStartAnalysisScope> GetSessionAnalysisScopeAsync( private async Task<HostSessionStartAnalysisScope> GetSessionAnalysisScopeAsync(
...@@ -117,14 +155,14 @@ internal partial class AnalyzerManager ...@@ -117,14 +155,14 @@ internal partial class AnalyzerManager
{ {
try try
{ {
return await GetSessionAnalysisScopeCoreAsync(analyzer, analyzerExecutor).ConfigureAwait(false); var task = GetSessionAnalysisScopeTask(analyzer, analyzerExecutor);
return await task.ConfigureAwait(false);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
// Task to compute the scope was cancelled. // Task to compute the scope was cancelled.
// Clear the entry in scope map for analyzer, so we can attempt a retry. // Clear the entry in scope map for analyzer, so we can attempt a retry.
Task<HostSessionStartAnalysisScope> cancelledTask; ClearSessionScopeMap(analyzer);
_sessionScopeMap.TryRemove(analyzer, out cancelledTask);
analyzerExecutor.CancellationToken.ThrowIfCancellationRequested(); analyzerExecutor.CancellationToken.ThrowIfCancellationRequested();
return await GetSessionAnalysisScopeAsync(analyzer, analyzerExecutor).ConfigureAwait(false); return await GetSessionAnalysisScopeAsync(analyzer, analyzerExecutor).ConfigureAwait(false);
...@@ -168,49 +206,84 @@ public async Task<GeneratedCodeAnalysisFlags> GetGeneratedCodeAnalysisFlagsAsync ...@@ -168,49 +206,84 @@ public async Task<GeneratedCodeAnalysisFlags> GetGeneratedCodeAnalysisFlagsAsync
} }
/// <summary> /// <summary>
/// Return <see cref="DiagnosticAnalyzer.SupportedDiagnostics"/> of given <paramref name="analyzer"/>. /// Compute <see cref="DiagnosticAnalyzer.SupportedDiagnostics"/> and exception handler for the given <paramref name="analyzer"/>.
/// </summary> /// </summary>
public ImmutableArray<DiagnosticDescriptor> GetSupportedDiagnosticDescriptors( private static Tuple<ImmutableArray<DiagnosticDescriptor>, EventHandler<Exception>> ComputeDescriptorsAndHandler(
DiagnosticAnalyzer analyzer, DiagnosticAnalyzer analyzer,
AnalyzerExecutor analyzerExecutor) AnalyzerExecutor analyzerExecutor)
{ {
var descriptors = _descriptorCache.GetOrAdd(analyzer, key => var supportedDiagnostics = ImmutableArray<DiagnosticDescriptor>.Empty;
// Catch Exception from analyzer.SupportedDiagnostics
analyzerExecutor.ExecuteAndCatchIfThrows(analyzer, () =>
{ {
var supportedDiagnostics = ImmutableArray<DiagnosticDescriptor>.Empty; var supportedDiagnosticsLocal = analyzer.SupportedDiagnostics;
if (!supportedDiagnosticsLocal.IsDefaultOrEmpty)
{
supportedDiagnostics = supportedDiagnosticsLocal;
}
});
// Catch Exception from analyzer.SupportedDiagnostics EventHandler<Exception> handler = null;
analyzerExecutor.ExecuteAndCatchIfThrows(analyzer, () => Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException = analyzerExecutor.OnAnalyzerException;
{ if (onAnalyzerException != null)
var supportedDiagnosticsLocal = analyzer.SupportedDiagnostics; {
if (!supportedDiagnosticsLocal.IsDefaultOrEmpty) handler = new EventHandler<Exception>((sender, ex) =>
{ {
supportedDiagnostics = supportedDiagnosticsLocal; var diagnostic = AnalyzerExecutor.CreateAnalyzerExceptionDiagnostic(analyzer, ex);
} onAnalyzerException(ex, analyzer, diagnostic);
}); });
EventHandler<Exception> handler = null; // Subscribe for exceptions from lazily evaluated localizable strings in the descriptors.
Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException = analyzerExecutor.OnAnalyzerException; foreach (var descriptor in supportedDiagnostics)
if (onAnalyzerException != null)
{ {
handler = new EventHandler<Exception>((sender, ex) => descriptor.Title.OnException += handler;
{ descriptor.MessageFormat.OnException += handler;
var diagnostic = AnalyzerExecutor.CreateAnalyzerExceptionDiagnostic(analyzer, ex); descriptor.Description.OnException += handler;
onAnalyzerException(ex, analyzer, diagnostic); }
}); }
// Subscribe for exceptions from lazily evaluated localizable strings in the descriptors. return Tuple.Create(supportedDiagnostics, handler);
foreach (var descriptor in supportedDiagnostics) }
{
descriptor.Title.OnException += handler; /// <summary>
descriptor.MessageFormat.OnException += handler; /// Return <see cref="DiagnosticAnalyzer.SupportedDiagnostics"/> of given <paramref name="analyzer"/>.
descriptor.Description.OnException += handler; /// </summary>
} public ImmutableArray<DiagnosticDescriptor> GetSupportedDiagnosticDescriptors(
DiagnosticAnalyzer analyzer,
AnalyzerExecutor analyzerExecutor)
{
// Check if the value has already been computed and stored.
Tuple<ImmutableArray<DiagnosticDescriptor>, EventHandler<Exception>> value;
lock (_descriptorMap)
{
if (_descriptorMap.TryGetValue(analyzer, out value))
{
return value.Item1;
} }
}
return Tuple.Create(supportedDiagnostics, handler); // Otherwise, compute the value.
}); // We do so outside the lock statement as we are calling into user code, which may be a long running operation.
value = ComputeDescriptorsAndHandler(analyzer, analyzerExecutor);
return descriptors.Item1; lock (_descriptorMap)
{
// Check if another thread already stored the computed value.
Tuple<ImmutableArray<DiagnosticDescriptor>, EventHandler<Exception>> storedValue;
if (_descriptorMap.TryGetValue(analyzer, out storedValue))
{
// If so, we return the stored value.
value = storedValue;
}
else
{
// Otherwise, store the value computed here.
_descriptorMap.Add(analyzer, value);
}
}
return value.Item1;
} }
/// <summary> /// <summary>
...@@ -221,47 +294,88 @@ internal void ClearAnalyzerState(ImmutableArray<DiagnosticAnalyzer> analyzers) ...@@ -221,47 +294,88 @@ internal void ClearAnalyzerState(ImmutableArray<DiagnosticAnalyzer> analyzers)
{ {
if (!analyzers.IsDefaultOrEmpty) if (!analyzers.IsDefaultOrEmpty)
{ {
foreach (var analyzer in analyzers) ClearDescriptorState(analyzers);
{ ClearAnalysisScopeState(analyzers);
ClearDescriptorState(analyzer);
ClearAnalysisScopeState(analyzer);
}
} }
} }
private void ClearDescriptorState(DiagnosticAnalyzer analyzer) private void ClearDescriptorState(ImmutableArray<DiagnosticAnalyzer> analyzers)
{ {
// Host is disposing the analyzer instance, unsubscribe analyzer exception handlers. lock (_descriptorMap)
Tuple<ImmutableArray<DiagnosticDescriptor>, EventHandler<Exception>> value;
if (_descriptorCache.TryRemove(analyzer, out value))
{ {
var descriptors = value.Item1; foreach (var analyzer in analyzers)
var handler = value.Item2;
if (handler != null)
{ {
foreach (var descriptor in descriptors) // Host is disposing the analyzer instance, unsubscribe analyzer exception handlers.
Tuple<ImmutableArray<DiagnosticDescriptor>, EventHandler<Exception>> value;
if (_descriptorMap.TryGetValue(analyzer, out value))
{ {
descriptor.Title.OnException -= handler; var descriptors = value.Item1;
descriptor.MessageFormat.OnException -= handler; var handler = value.Item2;
descriptor.Description.OnException -= handler; if (handler != null)
{
foreach (var descriptor in descriptors)
{
descriptor.Title.OnException -= handler;
descriptor.MessageFormat.OnException -= handler;
descriptor.Description.OnException -= handler;
}
}
_descriptorMap.Remove(analyzer);
} }
} }
} }
} }
private void ClearAnalysisScopeState(DiagnosticAnalyzer analyzer) private void ClearSessionScopeMap(DiagnosticAnalyzer analyzer)
{
lock (_sessionScopeMap)
{
_sessionScopeMap.Remove(analyzer);
}
}
private void ClearAnalysisScopeState(ImmutableArray<DiagnosticAnalyzer> analyzers)
{ {
// Clear session scope. // Clear session scope.
Task<HostSessionStartAnalysisScope> canceledTask; lock (_sessionScopeMap)
_sessionScopeMap.TryRemove(analyzer, out canceledTask); {
ClearSessionScopeMap_NoLock(analyzers);
}
// Clear compilation scope. // Clear compilation scope.
var keysToRemove = _compilationScopeMap.Keys.Where(analyzerAndOptions => analyzerAndOptions.Analyzer.Equals(analyzer)).ToImmutableArray(); lock (_compilationScopeMap)
{
ClearCompilationScopeMap_NoLock(analyzers);
}
}
private void ClearSessionScopeMap_NoLock(ImmutableArray<DiagnosticAnalyzer> analyzers)
{
foreach (var analyzer in analyzers)
{
_sessionScopeMap.Remove(analyzer);
}
}
private void ClearCompilationScopeMap_NoLock(ImmutableArray<DiagnosticAnalyzer> analyzers)
{
var keysToRemove = ArrayBuilder<AnalyzerAndOptions>.GetInstance();
var analyzersSet = analyzers.ToImmutableHashSet();
foreach (var analyzerAndOptions in _compilationScopeMap.Keys)
{
if (analyzersSet.Contains(analyzerAndOptions.Analyzer))
{
keysToRemove.Add(analyzerAndOptions);
}
}
foreach (var analyzerAndOptions in keysToRemove) foreach (var analyzerAndOptions in keysToRemove)
{ {
ConditionalWeakTable<Compilation, Task<HostCompilationStartAnalysisScope>> map; _compilationScopeMap.Remove(analyzerAndOptions);
_compilationScopeMap.TryRemove(analyzerAndOptions, out map);
} }
keysToRemove.Free();
} }
internal bool IsSupportedDiagnostic(DiagnosticAnalyzer analyzer, Diagnostic diagnostic, Func<DiagnosticAnalyzer, bool> isCompilerAnalyzer, AnalyzerExecutor analyzerExecutor) internal bool IsSupportedDiagnostic(DiagnosticAnalyzer analyzer, Diagnostic diagnostic, Func<DiagnosticAnalyzer, bool> isCompilerAnalyzer, AnalyzerExecutor analyzerExecutor)
......
...@@ -42,7 +42,18 @@ internal bool TryGetValue(TKey key, out TValue value) ...@@ -42,7 +42,18 @@ internal bool TryGetValue(TKey key, out TValue value)
// Store the value for the lifetime of the compilation. // Store the value for the lifetime of the compilation.
lock (_valueMap) lock (_valueMap)
{ {
_valueMap[key] = value; // Check if another thread already stored the computed value.
TValue storedValue;
if (_valueMap.TryGetValue(key, out storedValue))
{
// If so, we return the stored value.
value = storedValue;
}
else
{
// Otherwise, store the value computed here.
_valueMap.Add(key, value);
}
} }
return true; return true;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册