From 1c815d76a8f440154826f9a61f9d6f5e9710ed7c Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Thu, 3 Oct 2024 06:18:27 -0700 Subject: [PATCH] Reduce allocations and UI thread CPU costs in WithDoNotCreateCreationPolicy (#75358) This method shows up in the typing scenario in the new csharp editing speedometer test. CPU: This method accounts for 5% of UI thread CPU activity during typing. a) Removed the Enumerable.Count() call accounting for 1.7%. (Local testing showed limited value as there was typically a large number of states to iterate and a low number of syntax trees already realized) Allocations: This method accounts for 7.9% of allocations during typing. a) Removed ImmutableArray.Builder allocations: 0.2% b) Removed most SyntaxTree[] allocations: 0.2% (local testing showed very low hit rate of lazyCompilationWithoutGeneratedDocuments evalutation) c) Removed Enumerable.Count heap allocations due to boxing the ImmutableSortedDictionary enumerator: 0.2% --- ...pilationState.RegularCompilationTracker.cs | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RegularCompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RegularCompilationTracker.cs index cdd4f2d4548d..f63b4425e06a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RegularCompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RegularCompilationTracker.cs @@ -762,35 +762,34 @@ public ICompilationTracker WithDoNotCreateCreationPolicy() // partway through the logic in BuildInProgressStateFromNoCompilationStateAsync. If so, move those // parsed documents over to the new project state so we can preserve as much information as // possible. - - // Note: this count may be inaccurate as parsing may be going on in the background. However, it - // acts as a reasonable lower bound for the number of documents we'll be adding. - var alreadyParsedCount = this.ProjectState.DocumentStates.States.Count(static s => s.Value.TryGetSyntaxTree(out _)); - - // Specifically an ImmutableArray.Builder as we can presize reasonably and we want to convert to an - // ImmutableArray at the end. - var documentsWithTreesBuilder = ImmutableArray.CreateBuilder(alreadyParsedCount); - var alreadyParsedTreesBuilder = ImmutableArray.CreateBuilder(alreadyParsedCount); + using var _1 = ArrayBuilder.GetInstance(out var documentsWithTreesBuilder); foreach (var documentState in this.ProjectState.DocumentStates.GetStatesInCompilationOrder()) { - if (documentState.TryGetSyntaxTree(out var alreadyParsedTree)) - { + if (documentState.TryGetSyntaxTree(out _)) documentsWithTreesBuilder.Add(documentState); - alreadyParsedTreesBuilder.Add(alreadyParsedTree); - } } // Transition us to a state that only has documents for the files we've already parsed. + var documentsWithTrees = documentsWithTreesBuilder.ToImmutableAndClear(); var frozenProjectState = this.ProjectState .RemoveAllNormalDocuments() - .AddDocuments(documentsWithTreesBuilder.ToImmutableAndClear()); + .AddDocuments(documentsWithTrees); // Defer creating these compilations. It's common to freeze projects (as part of a solution freeze) // that are then never examined. Creating compilations can be a little costly, so this saves doing // that to the point where it is truly needed. - var alreadyParsedTrees = alreadyParsedTreesBuilder.ToImmutableAndClear(); - var lazyCompilationWithoutGeneratedDocuments = new Lazy(() => this.CreateEmptyCompilation().AddSyntaxTrees(alreadyParsedTrees)); + var lazyCompilationWithoutGeneratedDocuments = new Lazy(() => + { + using var _ = ArrayBuilder.GetInstance(documentsWithTrees.Length, out var alreadyParsedTrees); + foreach (var documentState in documentsWithTrees) + { + if (documentState.TryGetSyntaxTree(out var alreadyParsedTree)) + alreadyParsedTrees.Add(alreadyParsedTree); + } + + return this.CreateEmptyCompilation().AddSyntaxTrees(alreadyParsedTrees); + }); var lazyCompilationWithGeneratedDocuments = new CancellableLazy(cancellationToken => lazyCompilationWithoutGeneratedDocuments.Value);