Working with large .NET 5 solutions in Visual Studio 2019 16.8



With the release of .NET 5, migration of solutions from .NET Framework has increased. In particular, we have started to see very large solutions being moved. To ensure this experience is as good as possible, we have been working on optimizing Visual Studio to handle solutions that contain large numbers of .NET 5 and .NET Core projects. Many of these optimizations were shipped in the 16.8 release, and this blog post walks through the improvements we’ve made.

Running the C# and VB compiler out of process

Roslyn, the C# and Visual Basic compiler, parses and analyzes the entire solution to power services such as IntelliSense, Go to Definition, and diagnostics/errors. As a result, Roslyn tends to consume resources that increase proportionally with the size of the open solution, which can get quite significant for large solutions. Roslyn has already worked to minimize this impact by aggressively caching on disk information that isn’t immediately required, but even with that caching, it cannot escape the need to keep data in memory.

To reduce its impact on larger Visual Studio solutions, the Roslyn team has spent considerable effort moving the Roslyn compiler out of the Visual Studio process and into its own process. Running Roslyn in its own process frees up resources within Visual Studio itself and allows the Roslyn compiler more room to do its work. For large solutions, this can save up to a third of the memory consumed by Visual Studio when you open a large solution.

Streamlining the Dependencies node

Every .NET 5 and .NET Core project has a node in the Solution Explorer named “Dependencies” that displays all the things that the project depends on: other projects, assemblies, NuGet packages, etc. In addition to showing the immediate dependencies of the project, the node also shows the transitive dependencies of the project, i.e., all the things each dependency itself depends on, and so on. With a project of any reasonable size, this list of transitive dependencies can get quite large.

Unfortunately, the original implementation of the “Dependencies” node was not particularly efficient in the way that it stored the transitive dependency information in memory. It held on to much more information than was needed, and much of the data was redundant. We rewrote the code to keep only information that was absolutely required, and we started piggybacking on the existing information on dependencies already kept by NuGet. This rewrite saved up to 10-15% of the memory consumed by Visual Studio when you open a large solution.

Reducing duplicate information in MSBuild

After Roslyn, one of the other major consumers of resources in a Visual Studio process is MSBuild. This is because, as the build engine, much of the IDE experience is powered by MSBuild’s object model. Although we’ve made project files themselves much smaller in .NET 5 and .NET Core, there are a significant number of supporting project files that get imported into projects through the SDK. Evaluating all those files is necessary to understand and build a project, and can consume up to another third of the memory consumed by Visual Studio when you open a large solution.

Although project files generate a lot of data, much of that data is repetitive, and we have started de-duplicating that data in memory. Strings are one of the most common byproducts of the project system, and they store information like file names, options, and paths. Paths, specifically, can be quite long and can end up consuming a lot of memory if there are too many of them, or if they are duplicated too many times. Ensuring that we keep only one copy of a string saved up to 5-10% of the memory consumed by Visual Studio when you open a large solution.

Reducing project copies held onto by the project system

One of the important design aspects of the .NET 5 and .NET Core project system is asynchrony. Often the project system needs to do work in response to a user action (for example, adding a new reference to a project). Instead of blocking Visual Studio while it finishes its work, the project system allows the user to continue working in the IDE and does the work in the background.

Because the user can continue to make changes to a project (say, adding yet another reference) while the project system is processing previous changes in the background, the project system must save snapshots of the project data to ensure that a later action doesn’t conflict with an earlier one. As a result, the project system can easily end up with multiple copies of a project’s data in memory at once. If the project system is not careful about managing these copies, they may be held on to longer than needed, or even leaked and retained permanently. We’ve worked aggressively to reduce the number of copies we hold on to at one time.

Large solution load improvements

With all this work, we have significantly optimized the experience of working with large .NET 5 and .NET Core solutions. As of 16.8, in many of our tests we have seen a 2.5x improvement in the size of solution we can open before running into resource issues. We have also seen a decrease of up to 25% in crashes reported due to resource exhaustion.

The improvements listed above are just the beginning of the changes we are making to improve the experience of working with large solutions in Visual Studio. Individual solution performance still may vary, depending on the size of the solution, type of projects, extensions loaded, etc., and there are still areas that we are looking at to improve. We encourage any users who are experiencing issues with slowness or crashes loading solutions to contact us at so we can continue to improve the solution load experience for all solutions!


Leave a comment

  • Alfonso Ramos Gonzalez
    Alfonso Ramos Gonzalez

    It isn’t good enough. I was going to write a rant, but I decided to make a summary of the features I’d want instead:

    – Configure how many background tasks run concurrently.
    – Configure how many targets to run Roslyn against concurrently (always with priority to what is selected in the editor).
    – Show what the background tasks are doing (edit: in particular I would like to see if a particular rule is eating time).
    – Configure idle time required before begin running analyzers (don’t run on every keystroke).
    – Option to rerun analyzers per file.

    I’m running Visual Studio 16.8.4, by the way.

  • Avatar
    Martin Ba

    How does this affect large C++ solutions? The stuff about the dependency node and de-duplication inside MSBuild sounds as if this would benefit C++ projects as well?

    We’re currently working with a 850 projects (approx 90% C++, rest C#) sln and VS 2019 16.7.6

    I mean, holy cow is this already waaay better than it was with VS2017, but it would be interesting if we can expect performance improvements moving from 16.7 to 16.8 ??

  • Avatar
    Nicolas Musset

    How about finally making Visual Studio 64-bit? A feature that has been requested by developers for more than a decade.

    Usually are available RAM is not the bottleneck, I casually use development machines with between 16 to 128 GB of RAM. Every other projects at Microsoft has moved to 64-bit, all the WinAPI is available in 64-bit. What could possibly be the low-hanging fruits in Visual Studio codebase that prevent anyone from making it happen for the last 10 years? The usual response that was given some years ago that it will double the memory usage is risible. What if it does? We do have that memory available on our machines. I hope the team is aware that you are losing market share every time they delay this feature, to other IDEs such as JetBrains’ Rider that figured that out eons ago.

    I am working on a very large solution (500+ projects, 2 millions of SLOC). Since we have migrated all projects to the SDK format, it is impossible to open the whole solution without having VS freezing and eventually crashing with this very useless “hard error” window notification (with zero way to send a report about it).

    Our only workaround was to enable Resharper and disable everything using their Directory.Build.targets workaround detailed in their blog post from last year: That means that when Resharper is doing all the work that Roslyn and VS was supposed to do, it is still more performant while offering more features (its navigation and refactoring features are miles ahead of Roslyn offering). That doesn’t look good for the future of the VS team, or for Visual Studio as a product in general.

    Please get your priorities sorted out. You gave us a lot of useless stuff in the last few years such as this absolutely horrible new project window that is unusable compared to the classic one (that has now completely disappeared), and very slow to initialize.

        • Avatar
          Martin Sedlmair

          +1 I agree. 64bit is the way to go. Also on our side we are looking for alternatives (like Rider). Many of my team members have to kill VS approx. 5 times a day with the task manager because it simply hangs or crashes for itself. We only have around 250 projects in our solution. Fileing in issues and a trace report is pretty hard when you cannot do it while VS stops working 😉

    • Avatar
      Stephen White

      I am going to have to agree on this one. I really wish the VS team would stop hiding behind “Oh we’re making things out of process to make it better” excuse or staying silent on this matter. And please stop trying to push us towards using some hodge-podge online / cloud solution. That isn’t going to fly, especially if it requires us to get nickel and dimed out of Azure costs on top of VS licensing fees.

      Furthermore, I am going to have to agree on there being a lot of useless features. The new Git experience is terrible in VS compared to the old one that tightly integrated with Azure DevOps. Intellicode is a massive failure in my opinion and is useless bloat / garbage. You still do not have the snappiness of Resharper’s intellisense and your version of search everywhere is not as slick as Jetbrains. The list goes on and on…

      Sigh, I am sorry as coming off as harsh Microsoft, but I think you can see that a lot of us get frustrated with the state of VS as of late.

  • Avatar
    Andriy Savin

    Hi, while this all work is great, VS is still a pretty hungry on resources. I noticed that even with relatively small solutions when several VS instances are launched many other apps’ UI becomes quite slow. This is much more observable on WinForms/GDI applications, but for example windows file explorer becomes slow as well. I noticed with help of Spy++ that VS generates huge amount of windows messages even when it’s idle. This includes WM_TIMER events as well as many others. (also there are reservations of high-resolution timer for long periods). I have feeling that there is some system-wide lock for user32 resources, which becomes bottleneck when for example many messages are generated. I have 10 cores CPU, 64 Gigs of RAM, 2070 RTX GPU, NVME drive directly on PCIE, and with all that the UI still becomes noticeably slow! Once I close all VS instances, all app and windows explorer become much more responsive. I reported this bug couple of years ago, but perf is not getting better.

  • Avatar
    Rocco Balzamà

    Good morning,
    we are very interested in new developments in VisualStudio 2019,
    but before making a purchase, it would be possible to know when and if the new version “VisualStudio 2021” will be released

  • Avatar
    Ion Sorin Torjo

    All this crap would go away, if you actually made Visual Studio 64 bit. I simply can’t understand why you resist this so much.
    People have been asking for this for ages, and you keep on doing just workarounds.
    At some point, everyone will simply migrate to Rider (the only reason I haven’t done this yet is because it doesn’t support UWP debugging yet)