Time is your most precious asset and slow build is high in the list of developer’s productivity killers. With slow build the penalty is twofold:
- not only the time taken to build is lost,
- but often – during long enough build – your focus gets stolen by other tasks like mail check, coffee, social network… And when a programmer gets distracted, it takes a “good while” until she/he start editing code again.
Thus struggling for shorter build time is essential. This post discusses various tooling and strategies to Improve Visual Studio Build Performance:
Visualize and Measure Build Performance
Prior to improving one must first measure and understand. The Visual Studio extension Visual Studio Build Timer displays a live chart about your build in Visual Studio. This extension seems not well know and it doesn’t work yet with Visual Studio 2022. Nevertheless it can be an eye opener.
Our main solution for NDepend consists of 37 projects, including test projects. Compiling them all takes 43 seconds on my beefy laptop (64GB RAM, Xeon 6 Cores, 2x 1TB SSD). Btw, developing with a powerful machine is essential. Each saving done on hardware will likely costs a hundred more in terms of developer time and focus wasted.
Here is a GIF of our build on VS 2019 accelerated. Let’s hope this extension will be ported to Visual Studio 2022.
Visual Studio Solution Filters
More than 40 seconds build is a killer. As most of other development shops, we usually work with only a few projects loaded at a time. Thus it became a habit to unload manually unnecessary projects to achieve faster compilation. But the process of unloading projects set can be improved significantly by defining some solution filter files (.slnf extension). To create a solution filter just unload some projects, right click the solution item in the Solution Explorer panel, and click the menu Save as Solution Filter.
Visual Studio considers solution filter files (.slnf) as solution files (.sln) in the Start Window and also in MRU (Most Recently Used) menus. Also like a solution file, a filter file is a textual file that can be easily edited.
Loading a solution filter file just loads target projects and totally ignore others projects: they are not even shown as unloaded. The filter name is reminded in the top solution item to help.
It is up to you to define your solution filters. Some filters can be defined upfront: for example when some large projects are stable enough and not churned often. Some other filters are best defined at development time and can be ephemeral. For example when you realize that a particular refactoring task only impacts a few application and tests projects.
Keep in mind that unloading project comes with a downside: if the public API of a low-level project gets refactored with VS or R# refactoring tools, changes won’t be reflected in unloaded consumer projects.
Within the Visual studio context, MSBuild checks if a project output is up-to-date or not, to determine if it needs to be compiled. This feature is named Incremental Build and is essential to significantly reduces the number of projects compiled and thus, to achieve most of the time way faster build.
Common causes for a project for not being up-to-date are:
- One of the file used as project input (source file, resource file…) has a newer timestamp than the timestamp of a project output file (.dll, .pdb, .xml documentation…).
- The project depends on at least one project that is not up-to-date.
Incremental build checks cannot be perfect, especially when some non-trivial features are used. For example if one of the project file has the property Copy to Output Directory set to Copy Always, the project will never be considered as up-to-date. Such cause is not obvious and an investigation must be conducted to find the glitch. To investigate, in the Visual Studio > Options > Projects and Solutions > SDK-Style Projects, set the Up to Date Checks value from None to Minimal.
Now in the Output window we can see this log:
1>FastUpToDate: Item 'C:\Users\pat\source\repos\ClassLibrary29\ClassLibrary29\config.json' has CopyToOutputDirectory set to 'Always', not up to date. (ClassLibrary29)
Such log is a good hint of the cause of what forces the project to be recompiled every time. To solve this just set Copy to Output Directory to Copy if Newer.
The procedure to know if your solution has a problem with incremental build is easy: first use the command Build > Rebuild Solution then use the command Build > Build Solution. If some projects are recompiled the second time, then investigate.
Other Considerations to Improve Build Performance
First, this might sound obvious but things like Anti-Virus or running under a VM can significantly slow down all development tasks, especially builds.
Make sure you use parallel builds and also have “Only Build Startup and Dependencies on Run” in the Visual Studio option dialog: Project and Solutions > Build and Run:
Another point: By default Visual Studio compiles each project in its own .\bin\Debug directory and copy there all assemblies referenced by the project. I remember doing so was a real build performance killer with older Visual Studio versions like VS 2013 and earlier. Hopefully this has been fixed. Nevertheless if a project P is referenced by N other projects, there exists N+1 versions of the P assembly. Not only this waste of resource is not satisfying but it might be well that the N+1 versions of P get out-of-sync, for example because some projects are unloaded in the Visual Studio Solution Explorer. This can lead to some unexpected behaviors at run time, like getting a
MissingMethodException. This is why we make sure that all projects of a solution (except test projects) share the same output directory ..\bin\Debug. This way each assembly exists in a single version and we don’t waste resources.
Hot-Reload to skip Build
With the new .NET hot-reload feature (formerly known as Edit and Continue), the cycle [ code – build – launch – reach state to test – test it ] gets shortened since the steps [ build – launch – reach state to test ] can be skipped in many edit situations (but not all kind of edits are supported as explained here).
With hot reload, somehow source code changes get tracked, compiled and injected into the process to patch executed binaries. Interestingly enough injecting changes with hot reload is significantly faster than rebuilding the concerned Visual Studio project. It means that with Hot Reload the unit of build is finer-grained than the project/assembly level.
Most of the time the source code modifications between two builds represent a tiny fraction of the rebuilt code. We can imagine that in the future the build pipeline could benefit from this technology to improve the performance of incremental build by injecting code modifications in the binary instead of rebuilding the entire binary. This is purely speculative but here is a hint about the feasibility: In Visual Studio 2022 hot reload can be enabled to speed up test execution by skipping expensive builds for supported kind of edits. Notice this feature is still experimental and only works for .NET 6.
Do yourself and your team a favor by shortening build time for most builds. Invest in powerful hardware, use the right tools and conduct regularly investigations to determine what can be improved.