Skip to content

Parallelize loading solution-level analyzers#82447

Open
RikkiGibson wants to merge 3 commits intodotnet:mainfrom
RikkiGibson:parallel-solution-analyzer-load
Open

Parallelize loading solution-level analyzers#82447
RikkiGibson wants to merge 3 commits intodotnet:mainfrom
RikkiGibson:parallel-solution-analyzer-load

Conversation

@RikkiGibson
Copy link
Member

@RikkiGibson RikkiGibson commented Feb 18, 2026

I found this while investigating slow test perf in FileBasedProgramsWorkspaceTests.

I used test case TestSemanticDiagnosticsNotEnabledWhenCoarseGrainedFlagDisabled(False) as a reference. This test is not supposed to perform any design time builds, etc., so is expected to be "low cost" compared to many tests in this class.

  • Before the change, the test took ~9.6s to run on my machine. (note that the simple right-click->run case takes double this due to needing to also run the case with true argument.)
  • After the change, the test took about ~2.8s to run on my machine.
  • To get the times I just right-clicked->ran the single test several times. Execution time variance didn't look significant. I think that when running the whole class, some of the work is amortized, as I was seeing ~1s execution times in that case.
  • "Real world" startup of the language server is also likely to benefit from this change.

I was able to get some info using "Profile Test" in VS, and measuring "Async Activities". Unfortunately, it seems like the profiler doesn't have full ability to 'trace cause and effect', when, what we are essentially doing is sending a request into a stream, and another thread is reading it out. So, the information I got from the profiler, hinted at CreateTestLspServerAsync, and the Initialize request taking a long time specifically.

However, I wasn't able to get more details than that from the profiler data. Eventually I simply resorted to stepping thru the work the Initialize handler was doing, and literally just noticing when things 'felt slow', and finally sticking in Log() calls with timestamps to verify where the time was going. It would be fantastic to work out how to do this better in the future.

This is not great, and possibly represents me misusing the tools. But, in this case I was at least able to identify a major cause of the slowness. Creating an AnalyzerFileReference will cause us to crack the assembly file to get its name and stuff like that. Sometimes the wait time from that is significant. In practice there were ~45 analyzer DLLs to do this on. We were also doing essentially the exact same work back-to-back, so, sharing the resulting references array between host and misc workspaces, takes off ~0.5s from the test runtime.

image

@RikkiGibson RikkiGibson requested a review from a team as a code owner February 18, 2026 22:46
// 'CreateSolutionLevelAnalyzerReferences' needs to be broken out into its own service for us to be able to move this.
var miscellaneousFilesWorkspace = new LanguageServerWorkspace(hostServicesProvider.HostServices, WorkspaceKind.MiscellaneousFiles);
miscellaneousFilesWorkspace.SetCurrentSolution(s => s.WithAnalyzerReferences(CreateSolutionLevelAnalyzerReferencesForWorkspace(miscellaneousFilesWorkspace)), WorkspaceChangeKind.SolutionChanged);
Contract.ThrowIfFalse(
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 a basis for the assumption that reusing the same analyzer references array is fine.

Copy link
Member

Choose a reason for hiding this comment

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

It looks likes the AnalyzerFileReference may reference the actual DiagnosticAnalyzer / ISourceGenerator instances? I am not the most familiar here, but at a glance it seems dangerous to share them between two different workspaces?https://github.com/dotnet/roslyn/blob/main/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerFileReference.cs#L39
Maybe @jasonmalinowski is more familiar here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Neither of those types are aware of the workspaces layer, and, are expected to be able to be used with a variety of compilations, etc. in parallel, so, it feels unexpected for the sharing to be problematic. Let's definitely get confirmation though.

Copy link
Member Author

Choose a reason for hiding this comment

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

Note that if we went back to essentially doing this work twice, like before, we would probably add 0.5s to the wall clock time.

Copy link
Member

Choose a reason for hiding this comment

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

yeah if these are shareable, then this definitely makes sense to do. Just not sure on the contract here for that type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments