Recently I got involved in a big project where we had a single solution with approximately 100 projects.
Why 100 Projects in a Solution?
The reason for a 100 projects solution is that like in many modular systems these days, we have the following three tiers:
- A few core / common projects every project will use.
- A large amount of modules, independent of each other. This tier directly depends on tier 1.
- A few end-projects which load the different modules. This tier indirectly depends on tier 2.
So, yes, we could create several solutions with each tier compiling only when needed and using DLL reference instead of project references, but the amount of changes in all the tiers was still large enough and I’ve already seen this kind of build process fail miserably. So this was no go.
Build time took 15 minutes for the whole solution. Since we enforced a gated check-in policy in the company, this was really a pain point for the developers.
Note that the developers’ computers were strong enough, with 8GB ram, Intel Core i7 CPU and SSD disks.
So I started investigating what can be done to improve the situation.
Step 1: Build Projects in Parallel
Although the PC has 8 logical cores, the build system in Visual Studio 2010, when using C#, still uses only a single core! (Note: This is not the case in C++ build system.)
So after browsing the web, I found how you can manually trigger msbuild yourself as a Visual Studio external tool to compile your solution in parallel.
More details on how to set this up can be found in the following post by Scott Hanselman: http://www.hanselman.com/blog/HackParallelMSBuildsFromWithinTheVisualStudioIDE.aspx
After setting this up, I got an approximate build time of 10.2 minutes. Not bad for a few minutes of work! Also, got the following beautiful image out of my CPUs, where you can really see them at work:
Step 2: Beware of Copy Local = True
In our 100-projects solution, lots of projects reference each other, obviously. In addition to these references, we also reference several 3rd party components, practically from each module.
All the above caused that whenever we would compile the solution, over 4.5 GB of files were written. The majority (95%) of the writes were DLLs which were copied to the output folder of each project.
To check out how many writes are done in your compilation, check out this post.
Anyway, 4.5 GB takes a long time to write, even on an SSD drive.
So the next step was to eliminate those writes. To do this, we changed almost all of the “Copy local” settings in all the referenced DLLs from the default True to False. This will prevent the referenced DLLs to be copied to the output folder of each project.
Note: Some file names were blacked to protect the client’s properties.
In addition, we also changed the output folder of all the projects to a single folder, so all generated DLLs are copied to the one and only place where we actually run them. Doing so dropped the writes while compiling to under 200 MB, a huge time saver. Specifically, build time dropped to 7.5 minutes!
Step 3: Use RAM Disk
A RAM disk is a logical disk which resides entirely on the RAM.
It is extremely fast (faster than any SSD), but it is erased on every power-down, so only use it for temporary files.
Of course, you should have enough RAM to spare for this disk (the memory is pre-allocated for the disk use only), but on an 8GB PC, it’s usually not an issue.
There are several programs you can use to set up a RAM disk. I used DataRam RamDisk which supplies a free version with the ability to create a RAM disk up to 4 GB (1 or 2 GB should be sufficient for any build).
Configuration of the RAM disk is very easy:
After you download, install and format your new RAM disk, you can move your single output folder to it. If you want to keep your output folder in the same build drive, you can simply create a symbolic link between the current output folder and a folder on the RAM disk. This way makes the using of the RAM disk optional, only for users with sufficient memory.
To create a symbolic link between your build folder and your new RAM disk folder, use the following line:
mklink /D C:\Dev\MyCurrnetBuildFolder\Source\bin R:\bin
where R: is your RAM disk folder.
Note: You should change the path according to your build folder and your RAM drive settings.
Result: Adding the RAM disk reduced compilation time to less than 5 minutes! This is a 66% reduction of the original time!
In this post, we’ve seen how you can decrease build time. Of course, I can’t make any guarantees. Every project has its own characteristics and problems, but the steps provided can probably reduce the build time if you fit the profile of standard line-of-business applications.
That’s it for now,
Arik Poznanski is a Senior Consultant and Instructor at Sela Technology Center. He completed two B.Sc. degrees in Mathematics & Computer Science, summa cum laude, from the Technion in Israel.
Arik has extensive knowledge and experience in many Microsoft technologies, including .NET with C#, WPF, Silverlight, WinForms, Interop, COM/ATL programming, C++ Win32 programming and reverse engineering (assembly, IL).