Solutions which contain a high amount of projects or code may be time consuming in loading and compiling the code within Visual Studio. Generally, large solutions running on modest hardware may run the risk of Visual Studio crashes because of high memory footprint, if for example working with complex XAML files in designer mode.
Ways of Structuring Solution Files
Large amounts of code can be structured in different ways:
- One big solution file
- Loading and compilation time as well as memory footprint will increase massively with every additional project. If working in C++ mixed mode for example, the overall development performance of Visual Studio 2008/2010 slows down significantly.
With this approach, the Visual Studio solution is the modularization unit, all projects are contained in one large solution as outlined in the following picture:
- Multiple solution files (e.g. one sln file for each feature of the software project)
- One big master solution with multiple smaller generated solutions
- It is a modification of the "multiple solutions" approach.
There are some tools for automatic solution generations. Personally I prefer to have the control over sln and project files by the development team, not auto-magically generated. Also, it’s often not possible to have a high amount of small and isolated solutions due to inter-project dependencies (see the picture above).
Since the approach of working with many solution files is not optimal especially due to maintenance reasons (e.g. the build and CI processes can be challenging with many solutions), I was looking for mitigations of the ‘one big solution‘ approach. Some mitigations would be:
- Maintaining multiple copies of suo files (which contain the loaded/unloaded status of the projects)
- The suo files are binary and can’t be shared among team members. They also contain a lot of features like bookmarks, break points, list of open documents, etc. which are not necessary for project loadings.
- Loading/unloading multiple projects in the Solution Explorer at the same time is quite time consuming.
- Writing macros (probably specific for the current solution and development team) enabling compilation of the aspects.
In addition, the following techniques can be applied to improve the build performance:
- Using tools for improving build performance like ’IncrediBuild’ or ’Electric Cloud’. Although this approach can decrease the compilation duration significantly, the solution load duration still remains the same. By the way, some of these tools are quite expensive.
- Hardware / OS level: Multi core compilation, using SSD and a high amount of RAM, using of Memory RAM for ’bin/obj’ directories, etc.
Load Only the Required Projects
Even if all or some of the mitigations mentioned above are applied, there is still one issue: In most cases, we work on a single feature or defect, so that only a few projects are relevant for the current aspect of work. But instead of concentrating on those few projects, we have to drag the burden of loading and compiling many (currently) irrelevant projects just because there are some technical dependencies or just because they are part of the same business project and are referenced by the sln file. So if loading of irrelevant projects for the current task could be avoided, this provided a huge benefit in loading and compiling times.
If we just modularized our code within one single solution, it gave us a huge benefit in development efficiency. With this approach, it should be possible to work with different dedicated aspects of the solution, loading and compiling only the projects of the particular aspect as outlined in the following picture:
Since I couldn’t find any convenient enough solution for the described approach, I wrote a Visual Studio extension called Funnel.
With Funnel, you can just keep all the code in one single solution but structure the code by leveraging load filters. It’s even possible to combine several load filters and so load several aspects at once. It’s also possible to just load all (or none) of the solution projects regardless of the filter definitions.
In order to fully leverage Funnel, a load filter has to be defined for each solution aspect. This can be done on-demand – as soon as you are going to work with a particular aspect which does not have a filter yet, just define a new filter.
In the following picture, two filters “ASP.NET MVC” and “Start UI” are combined at load time, so that only 3 projects contained by these filters are loaded and compiled. All other projects remain in the “unloaded” state as long as these filters are applied.
In the next picture, only the profiler relevant projects (which are part of the profile filter) are loaded.
The filters are stored as plain XML files and can be shared by team members in a source control repository.
Funnel is available for download in the Visual Studio Gallery, it can be used free of charge for all kinds of projects: commercial and non-commercial. A Getting-Started as well as up-to-date release notes are available at vsext.com.
All the above description was quite theoretical, let us get to the world of code and tools.
I prepared a screen cast with a demonstration of the Funnel extension. The code used can be downloaded from Source Forge, so that you will be able to play with the demonstration code base by yourself.
In the screen cast, we are going to create a small “bug fix” in the existing SharpDevelop source code. I used the real SharpDevelop code which was publicly available at the time of publishing this article (beginning of November 2013) at Source Forge. You will find a brief description of the executed steps after the screen cast.
See the screen cast at youtube.
Brief Demo Description
SharpDevelop provides a New View dialog for ASP.NET MVC 4 projects. In this dialog, the user can choose the view engine: 'ASPX' or 'Razor'. The current default selection is 'ASPX' but we are going to change it to 'Razor' in our demonstration.
- Before the demo, the whole solution must be compiled once so that all project dependencies are fulfilled. For SharpDevelop, it can be done with debugbuild.bat located in the root source directory. We don’t need all project at once afterwards, but it has to be done once in order to create all binaries.
- We are going to define 1st load filter. For this step, we don’t need any projects, so that we just open the SharpDevelop solution with the option “Load None”, so that no projects are loaded.
- A filter for the start up project is defined. We can reload the solution with this load filter and start the project.
- We start SharpDevelop, open an existing ASP.NET MVC project and see that the New View dialog contains ASPX syntax as default engine. We are going to change the default engine to Razor – this is our “bugfix” goal.
- Break the debugging session. Create a new filter for ASP.NET MVC aspect and reload the solution with the combination of the both existing load filters.
- Now we have 3 loaded projects. We can do our bugfix - adjust the selected index of the engine combo box, recompile and run the solution again.
- Start SharpDevelop and see that the New View dialog now contains the Razor engine as default selection.
- 14th October, 2013: Initial version