Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance of FAR #73523

Merged
merged 5 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,14 @@

using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.FindUsages;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
Expand Down Expand Up @@ -241,10 +243,34 @@ public static SerializableDefinitionItem Dehydrate(int id, DefinitionItem item)
}
}

[DataContract]
internal readonly struct SerializableClassifiedSpansAndHighlightSpan(
SerializableClassifiedSpans classifiedSpans, TextSpan highlightSpan)
{
private static readonly ObjectPool<SegmentedList<ClassifiedSpan>> s_listPool = new(() => new());

[DataMember(Order = 0)]
public readonly SerializableClassifiedSpans ClassifiedSpans = classifiedSpans;

[DataMember(Order = 1)]
public readonly TextSpan HighlightSpan = highlightSpan;

public static SerializableClassifiedSpansAndHighlightSpan Dehydrate(ClassifiedSpansAndHighlightSpan classifiedSpansAndHighlightSpan)
=> new(SerializableClassifiedSpans.Dehydrate(classifiedSpansAndHighlightSpan.ClassifiedSpans), classifiedSpansAndHighlightSpan.HighlightSpan);

public ClassifiedSpansAndHighlightSpan Rehydrate()
{
using var pooledObject = s_listPool.GetPooledObject();
this.ClassifiedSpans.Rehydrate(pooledObject.Object);
return new ClassifiedSpansAndHighlightSpan(pooledObject.Object.ToImmutableArray(), this.HighlightSpan);
}
}

[DataContract]
internal readonly struct SerializableSourceReferenceItem(
int definitionId,
SerializableDocumentSpan sourceSpan,
SerializableClassifiedSpansAndHighlightSpan classifiedSpans,
SymbolUsageInfo symbolUsageInfo,
ImmutableDictionary<string, string> additionalProperties)
{
Expand All @@ -255,22 +281,26 @@ internal readonly struct SerializableSourceReferenceItem(
public readonly SerializableDocumentSpan SourceSpan = sourceSpan;

[DataMember(Order = 2)]
public readonly SymbolUsageInfo SymbolUsageInfo = symbolUsageInfo;
public readonly SerializableClassifiedSpansAndHighlightSpan ClassifiedSpans = classifiedSpans;

[DataMember(Order = 3)]
public readonly SymbolUsageInfo SymbolUsageInfo = symbolUsageInfo;

[DataMember(Order = 4)]
public readonly ImmutableDictionary<string, string> AdditionalProperties = additionalProperties;

public static SerializableSourceReferenceItem Dehydrate(int definitionId, SourceReferenceItem item)
=> new(definitionId,
SerializableDocumentSpan.Dehydrate(item.SourceSpan),
// We're always have classified spans for C#/VB, which are the only languages used in OOP find-references.
SerializableClassifiedSpansAndHighlightSpan.Dehydrate(item.ClassifiedSpans!.Value),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the computation always happens here:

var options = await optionsProvider.GetOptionsAsync(document.Project.Services, cancellationToken).ConfigureAwait(false);
var documentSpan = new DocumentSpan(document, sourceSpan);
var classifiedSpans = await ClassifiedSpansAndHighlightSpanFactory.ClassifyAsync(
documentSpan, classifiedSpans: null, options, cancellationToken).ConfigureAwait(false);
return new SourceReferenceItem(
definitionItem, documentSpan, classifiedSpans, referenceLocation.SymbolUsageInfo, referenceLocation.AdditionalProperties);

we just were dropping the value on the floor here.

item.SymbolUsageInfo,
item.AdditionalProperties);

public async Task<SourceReferenceItem> RehydrateAsync(Solution solution, DefinitionItem definition, CancellationToken cancellationToken)
=> new(definition,
await SourceSpan.RehydrateAsync(solution, cancellationToken).ConfigureAwait(false),
// Todo: consider serializing this over.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. this was a good thing to consider :D

classifiedSpans: null,
this.ClassifiedSpans.Rehydrate(),
SymbolUsageInfo,
AdditionalProperties.ToImmutableDictionary(t => t.Key, t => t.Value));
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ static async Task<FindReferenceCache> ComputeCacheAsync(Document document, Cance
// Find-Refs is not impacted by nullable types at all. So get a nullable-disabled semantic model to avoid
// unnecessary costs while binding.
var model = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var nullableEnableSemanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

// It's very costly to walk an entire tree. So if the tree is simple and doesn't contain
// any unicode escapes in it, then we do simple string matching to find the tokens.
var index = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false);

return new(document, text, model, root, index);
return new(document, text, model, nullableEnableSemanticModel, root, index);
}
}

Expand All @@ -54,18 +55,27 @@ static async Task<FindReferenceCache> ComputeCacheAsync(Document document, Cance
public readonly ISyntaxFactsService SyntaxFacts;
public readonly SyntaxTreeIndex SyntaxTreeIndex;

/// <summary>
/// Not used by FAR directly. But we compute and cache this while processing a document so that if we call any
/// other services that use this semantic model, that they don't end up recreating it.
/// </summary>
#pragma warning disable IDE0052 // Remove unread private members
private readonly SemanticModel _nullableEnabledSemanticModel;
#pragma warning restore IDE0052 // Remove unread private members
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is part of the change. ideally nothing will be using nullable-enabled-semantic-model (And i want to stamp out anything that is using it). but if they do, this at least means we cache the results while processing the doc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

next week i'll be setting BPs in the normal 'get semantic model' codepath so that i can update any final services still using it during FAR to stop.


private readonly ConcurrentDictionary<SyntaxNode, SymbolInfo> _symbolInfoCache = [];
private readonly ConcurrentDictionary<string, ImmutableArray<SyntaxToken>> _identifierCache;

private ImmutableHashSet<string>? _aliasNameSet;
private ImmutableArray<SyntaxToken> _constructorInitializerCache;

private FindReferenceCache(
Document document, SourceText text, SemanticModel semanticModel, SyntaxNode root, SyntaxTreeIndex syntaxTreeIndex)
Document document, SourceText text, SemanticModel semanticModel, SemanticModel nullableEnabledSemanticModel, SyntaxNode root, SyntaxTreeIndex syntaxTreeIndex)
{
Document = document;
Text = text;
SemanticModel = semanticModel;
_nullableEnabledSemanticModel = nullableEnabledSemanticModel;
Root = root;
SyntaxTreeIndex = syntaxTreeIndex;
SyntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
Expand Down
Loading