Skip to content

Optimize OOP compilation of HasSourceGenerators.#72978

Merged
CyrusNajmabadi merged 5 commits intodotnet:mainfrom
CyrusNajmabadi:lessTesxtSync
Apr 11, 2024
Merged

Optimize OOP compilation of HasSourceGenerators.#72978
CyrusNajmabadi merged 5 commits intodotnet:mainfrom
CyrusNajmabadi:lessTesxtSync

Conversation

@CyrusNajmabadi
Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi commented Apr 11, 2024

We had two problems with the prior approach.

  1. the information was cached with ProjectStates. But htat meant if the project-state cahnged (which it does on any doc-change) we would recompute if the project had generators.
  2. when calling to OOP to have it determine if the project had generators, we would do a full-project sync (which included document contents). So, again, when a doc changed, this would increase the cost of computing this info.

The new approach is much more lightweight.

First, On the host side, we now cache the information along with the list-of-analyzer-references a project has. That list almost never changes when projects fork (unless hte user literally adds/removes references). So the information is reused virtually all the time on the host side.

Second, when we do make the call to oop, oop only needs to sync over the analyzer references, which it can do in in a much more lightweight fashion. Oop also caches the information on a per-analyzer-reference, so that different projects which reference the same analyzer references can call to oop and have the question answered the first time without a call back to the host at all.

--

https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_build/results?buildId=9398165&view=results
https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_build/results?buildId=9398506&view=results

@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner April 11, 2024 04:20
@ghost ghost added Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead labels Apr 11, 2024
@CyrusNajmabadi
Copy link
Contributor Author

Going to to a test insertion to see if this has any perf issues.

solution,
projectId,
(service, solution, cancellationToken) => service.HasGeneratorsAsync(solution, projectId, cancellationToken),
(service, solution, cancellationToken) => service.HasGeneratorsAsync(solution, projectId, analyzerReferences, projectState.Language, cancellationToken),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

new approach passes along the analyzer-reference checksums and language, as we can answer on the oop side with just that info.

private static readonly Dictionary<string, AnalyzerReferenceMap> s_languageToAnalyzerReferenceMap = new()
{
{ LanguageNames.CSharp, new() },
{ LanguageNames.VisualBasic, new() },
Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's fine to have these be keyed just by VB/C#. OOP is only supported for those two languages alone. This makes it easy to initialize and look into this without locking, or complex types like ConcurrentDict.

{
return RunServiceAsync(solutionChecksum, async solution =>
{ LanguageNames.CSharp, (new(), static analyzerReference => HasSourceGenerators(analyzerReference, LanguageNames.CSharp)) },
{ LanguageNames.VisualBasic, (new(), static analyzerReference => HasSourceGenerators(analyzerReference, LanguageNames.VisualBasic)) },
Copy link
Contributor Author

Choose a reason for hiding this comment

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

same here. we can hardcode in C#/vb here to simplify thigns.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

note: we cache thsi per analyzer reference so that multiple projects with the same analyzer references (common), and lang, will cache the same result.

string language,
CancellationToken cancellationToken)
{
if (analyzerReferenceChecksums.Length == 0)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

technically not necessary. the host side already checked this. d

@CyrusNajmabadi
Copy link
Contributor Author

https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/543307 shows no regressions. (There is one regression, but it was caused by #72965 not this PR).

var analyzerReferenceMap = s_languageToAnalyzerReferenceMap[projectState.Language];
if (!analyzerReferenceMap.TryGetValue(projectState.AnalyzerReferences, out var lazy))
{
// Extracted into local function to prevent allocations in the case where we find a value in the cache.
Copy link
Contributor

Choose a reason for hiding this comment

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

// Extracted into local function to prevent allocations in the case where we find a value in the cache.

Could we just make static and get rid of this comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

my guess is no. there are needed captures in these paths. but i will try.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, it's all sorts of captury. :) No go on static here. We need to capture enough data to be able to call to oop effectively to answer the question. note that once this happens once, and is cached, that captured data will be dropped. So we only need this alloc when looking at a fresh (non-identity) list of analyzer-references. Which should be very rare as the solution forks forwards.

@CyrusNajmabadi CyrusNajmabadi requested a review from ToddGrun April 11, 2024 18:10
Copy link
Contributor

@ToddGrun ToddGrun left a comment

Choose a reason for hiding this comment

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

:shipit:

@CyrusNajmabadi
Copy link
Contributor Author

@jasonmalinowski For review when you get back.

@dibarbet dibarbet modified the milestones: Next, 17.11 P1 Apr 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants