-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Slow design-time builds for large solution #27738
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
Comments
Team Triage: Thanks for filing a great bug/repro! Hopefully we'll be able to dedicate some time to analyze this in the coming weeks! |
Could you share the binlog? |
Sure, it is 40MB though, so it is split into two parts:
|
Is there a reason why maxcpucount is /m:1? There is a strange 3s gap at the 63s mark. |
Adding more MSBuild processes doesn't improve performance that much (maybe by 50% at max) and it definitely makes the analysis much harder.
This list is what Rider uses for design-time builds (it is possible to collect binary logs with Rider by checking Help -> Diagnostic Tools -> Enable Design Time Logging). VisualStudio uses a different list of targets (see docs on how to get logs from VS), but to my understanding, these are targets defined internally in VS so it is not as easy to call them from the CLI.
The point is to mimic the design-time build behavior as close as possible. We could focus on a single target for now but the goal is to make the entire design-time build performant.
Actually, IDEs don't run the design time build for the entire sln at once - they make a new request for each individual project, so your PR will not help in this case.
I'm using a mobile i7-8550u, but I've just tested it on the much faster AMD EPYC 7571 and still, it took ~2 min. Did you build the solution, before running the design-time build? When it is not built it is much faster, but that is not the use case we care about. |
What matters is running That's what makes things like Ionide.ProjInfo able to populate compiler options for F# code analysis using FSharp.Compiler.Service. Without a restore lots of things fail early and are thus much quicker. I ran a slightly modified command - mainly different logging. Full command (click to expand)
Results:
CPU: Ryzen 5700g (8-core / 16-thread)
Worth noting that it is well known (lots of experiments done) at our company that The below results demonstrate that:
To clarify: IDEs (at least Rider) already parallelise this completely by sending multiple requests to MSBuild in parallel, one per project. Rider defaults to /m:1Will describe this Rider example just as another confirmation that this is a widespread issue: Rider has a semi-recent setting that allows one to increase the number of MSBuild processes for solution loading. You can also see that the full build does automatically use lots of processes (14/16 in this case). |
I did some profiling and realized that GC plays quite a role in slowing the main MSBuild process down. I then ran the following test with and without server GC:
I used 7 MSBuild parallel processes as show in the above comment. This yielded the following results/logs in Rider's backend (logs that Rider outputs about MSBuild design-time build requests upon solution load): gcServer=0 (using workstation GC):
gcServer=1 (using server GC):
So forcing server GC for MSBuild (all) node processes yielded 32% reduction in design-time build 🎉 Below I'm attaching screenshots from DotTrace profiling the main MSBuild node. Workstation GCFor reference, here is a ticket I raised with JetBrains to consider enabling Server GC for design-time MSBuild: https://youtrack.jetbrains.com/issue/RIDER-80141/Consider-forcing-server-GC-for-MSBuild-processes-or-at-least-provide-a-button-to-opt-in Do we know if Visual Studio already has that option enabled? I'm aware that if too many processes are spawned, all using Server GC, they might start throttling each other too much and causing too much context switching. |
I tried various /m and between /m:4 to /m:8 yielded the best results. 25s to 11s
I have noticed this too. dotnet/msbuild#7625 I haven't noticed an improvement with gcServer. Perhaps there are other factors involved or it is machine dependent. |
I took perfview traces and binlogs of this repro (sln open in VS). But when looking at project system what strikes me was My knowledge of SDK targets, nuget lock files and DTB is very limited, at best, so I can't tell if this is expected, inefficient or bug. To me it is just a little bit suspicious. |
I thought that @tmeschter @drewnoakes Can you clarify whether the |
The change @dsplaisted is referring to was to stop returning the full list of all transitively-reachable package references and their contents to the project system, in order to display them in the dependencies tree. After the change, only the top level dependencies were returned, and transitive dependencies were populated lazily as the user expands the tree (or during search in Solution Explorer). I'm not intimate with the details, but I would expect ResolvePackageDependencies to still be called as part of reference resolution. The DTB needs to resolve referenced assemblies (including those coming from packages) in order to pass that information to Roslyn's language service. |
Skimming through a DTB binlog, it seems that the output of ResolvePackageDependencies ends up feeding through ResolveAssemblyReferences. |
Looking at this more, it looks like However, it still has to read the assets file. It uses an in-memory cache for this so it won't read the same assets file multiple times in the same build, but it looks like this may still be a perf hit in this scenario with a lot of projects in the solution. Most of the logic for reading the assets file is now in @dotnet/msbuild @marcpopMSFT |
@rokonec did you want to look into making the SDK side change to have ResolvePackageAssets output the values that RPD currently gets or did you want the SDK to take a look? |
The pr added two new properties to the NuGet assets cache file, so it will invalidate the old one. Please clean the solution and run NuGet restore. |
This should be fixed in 7.0.200 via #28405. Thanks a bunch @marcin-krystianc for the detailed information which enabled us to investigate and address this! |
Improves dotnet/sdk#27738 It is another attempt to introduce changes proposed in #8098, which was reverted due to a bug.
Issue Description
Hi,
productivity in our company is heavily hindered by the slowness of design-time builds which translates into the slowness of solution load (and reload) times in the IDE (Visual Studio and Rider).
For example, we are working with solution containing about 270 C# and F# projects and use Central Package Management to manage version of our NuGet dependencies. The load time of such solution is about 2-3 minutes which is far too long to stay focused.
The fact that we use a mix of C# and F# projects is probably irrelevant because according to my internal testing the solution load time when all projects are C# projects is the same. Also, the usage of central package version management seems to be not really affecting how long it takes to run an individual design-time build. But the concerning consequence of using central package management is that any slightest change in the central file (
Directory.Packages.Props
) triggers a reload of all projects in the solution so the slowness of design-time builds is exacerbated by central package management.We've prepared a skeleton version of our solution https://github.com/marcin-krystianc/TestSolutions/tree/master/LargeAppWithPrivatePackagesCentralisedNGBVRemoved which can be used for testing.
I don't think that the problem is unique to our solution though, my suspicion (confirmed with some experiments) is that any solution of similar size has similar performance characteristics. So, given that the problem is relevant to all large solutions, we are wondering whether there are any plans to improve the performance of design-time builds for large solutions? Maybe a static graph feature can help? It turned out to be a huge win for NuGet restores (https://devblogs.microsoft.com/visualstudio/performance-improvements-in-nuget/#msbuild-static-graph-evaluation) so maybe it is possible to apply it for design-time builds as well?
Are you able to provide any hints on where to look for any improvements? Looking at text logs or binary logs (
/bl
) didn't give any obvious answers so far.Steps to Reproduce
LargeAppWithPrivatePackagesCentralisedNGBVRemoved\solution
directorydotnet build
Data
Sample logs from the command above. Please note, that this is not exactly the same as loading the solution in IDE, because IDE runs desgn-time build for each project individually wehereas we run it for entire solution all at once:
Analysis
/m
) doesn't really make it much faster as it seems that msbuild utilised more machine resources, but at the same time it also does much more work as caching inside msbuild is less effective.Versions & Configurations
Regression?
I don't think so.
The text was updated successfully, but these errors were encountered: