Skip to content

Commit

Permalink
Reduce allocations and UI thread CPU costs in WithDoNotCreateCreation…
Browse files Browse the repository at this point in the history
…Policy (#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%
  • Loading branch information
ToddGrun authored Oct 3, 2024
1 parent 0d41e66 commit 1c815d7
Showing 1 changed file with 15 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<DocumentState>(alreadyParsedCount);
var alreadyParsedTreesBuilder = ImmutableArray.CreateBuilder<SyntaxTree>(alreadyParsedCount);
using var _1 = ArrayBuilder<DocumentState>.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<Compilation>(() => this.CreateEmptyCompilation().AddSyntaxTrees(alreadyParsedTrees));
var lazyCompilationWithoutGeneratedDocuments = new Lazy<Compilation>(() =>
{
using var _ = ArrayBuilder<SyntaxTree>.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<Compilation?>(cancellationToken => lazyCompilationWithoutGeneratedDocuments.Value);

Expand Down

0 comments on commit 1c815d7

Please sign in to comment.