diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs index e7ec3df4574212a5dbb2c1a806d943f4b55f44f5..8ab6bf298de07119dd2a7dfd7ace18099ad59b06 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs @@ -15,6 +15,7 @@ internal partial class AnalysisState { private class PerAnalyzerState { + private const int SymbolLimitForPooling = 100; private readonly object _gate = new object(); private readonly Dictionary _pendingEvents = new Dictionary(); private readonly Dictionary _pendingSymbols = new Dictionary(); @@ -37,6 +38,48 @@ private class PerAnalyzerState _currentlyAnalyzingDeclarationsMapPool = currentlyAnalyzingDeclarationsMapPool; } + /// + /// Returns true if the object should be returned to the pool. + /// + public bool Free() + { + lock (_gate) + { + foreach (var analyzerStateData in _pendingEvents.Values) + { + FreeState_NoLock(analyzerStateData, _analyzerStateDataPool); + } + + foreach (var analyzerStateData in _pendingSymbols.Values) + { + FreeState_NoLock(analyzerStateData, _analyzerStateDataPool); + } + + foreach (var declarationDataMap in _pendingDeclarations.Values) + { + foreach (var declarationStateData in declarationDataMap.Values) + { + FreeDeclarationAnalyzerState_NoLock(declarationStateData); + } + + FreeDeclarationDataMap_NoLock(declarationDataMap); + } + + // If we have too many symbols then just discard the state object from the pool - we don't want to hold onto really large dictionaries. + if (_pendingSymbols.Count > SymbolLimitForPooling) + { + return false; + } + + _pendingEvents.Clear(); + _pendingSymbols.Clear(); + _pendingDeclarations.Clear(); + _lazySyntaxTreesWithAnalysisData = null; + _pendingSyntaxAnalysisTreesCount = 0; + return true; + } + } + public void AddPendingEvents(HashSet uniqueEvents) { lock (_gate) @@ -275,7 +318,7 @@ private void FreeDeclarationAnalyzerState_NoLock(DeclarationAnalyzerStateData st private static void FreeState_NoLock(TAnalyzerStateData state, ObjectPool pool) where TAnalyzerStateData : AnalyzerStateData { - if (state != null) + if (state != null && !ReferenceEquals(state, AnalyzerStateData.FullyProcessedInstance)) { state.Free(); pool.Free(state); diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs index 21f0c04280aec9b346ad2a4275197c803a6a99da..a642d24adc3f7b89ee344ce2305d053f35cca5ef 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs @@ -63,6 +63,13 @@ internal partial class AnalysisState private readonly ObjectPool> _compilationEventsPool; private readonly HashSet _pooledEventsWithAnyActionsSet; + // Create static pools for heavily allocated per-analyzer state objects - this helps in reducing allocations across CompilationWithAnalyzer instances. + private const int PoolSize = 5000; + private static readonly ObjectPool s_analyzerStateDataPool = new ObjectPool(() => new AnalyzerStateData(), PoolSize); + private static readonly ObjectPool s_declarationAnalyzerStateDataPool = new ObjectPool(() => new DeclarationAnalyzerStateData(), PoolSize); + private static readonly ObjectPool> s_currentlyAnalyzingDeclarationsMapPool = new ObjectPool>(() => new Dictionary(), PoolSize); + private static readonly ObjectPool s_perAnalyzerStatePool = new ObjectPool(() => new PerAnalyzerState(s_analyzerStateDataPool, s_declarationAnalyzerStateDataPool, s_currentlyAnalyzingDeclarationsMapPool), PoolSize); + public AnalysisState(ImmutableArray analyzers, CompilationData compilationData) { _gate = new object(); @@ -80,19 +87,33 @@ public AnalysisState(ImmutableArray analyzers, CompilationDa _pooledEventsWithAnyActionsSet = new HashSet(); } - private static ImmutableDictionary CreateAnalyzerStateMap(ImmutableArray analyzers, out ImmutableArray analyzerStates) + ~AnalysisState() { - var analyzerStateDataPool = new ObjectPool(() => new AnalyzerStateData()); - var declarationAnalyzerStateDataPool = new ObjectPool(() => new DeclarationAnalyzerStateData()); - var currentlyAnalyzingDeclarationsMapPool = new ObjectPool>( - () => new Dictionary()); + // Free the per-analyzer state tracking objects. + foreach (var analyzerState in _analyzerStates) + { + var shouldReturnToPool = analyzerState.Free(); + + // If we have too many symbols then just discard the state object from the pool - we don't want to hold onto really large dictionaries. + if (shouldReturnToPool) + { + s_perAnalyzerStatePool.Free(analyzerState); + } + else + { + s_perAnalyzerStatePool.ForgetTrackedObject(analyzerState); + } + } + } + private static ImmutableDictionary CreateAnalyzerStateMap(ImmutableArray analyzers, out ImmutableArray analyzerStates) + { var statesBuilder = ImmutableArray.CreateBuilder(); var map = ImmutableDictionary.CreateBuilder(); var index = 0; foreach (var analyzer in analyzers) { - statesBuilder.Add(new PerAnalyzerState(analyzerStateDataPool, declarationAnalyzerStateDataPool, currentlyAnalyzingDeclarationsMapPool)); + statesBuilder.Add(s_perAnalyzerStatePool.Allocate()); map[analyzer] = index; index++; }