Speed-up VC++ builds

The larger a VC++ project is (or any project for that matter) the more time it takes to build. If you have solutions with many large projects the build times could get into your way. Starting a rebuild and going to lunch was a common scenario for me, as building took 20-25 minutes. Of course, you don’t rebuild everything all the time, but sometimes is enough to change something in a header somewhere deep inside the dependency web and trigger a rebuild of many or all the projects in a solution.

Using compiler switches and other tricks I managed to reduce the build time with about 50% or more (the percent of speedup is similar on different machines, regardless of the previous build times on those machines). Replacing the hard disk with an SSD provided an additional 25% speed. My full build times have decreased from 20-25 minutes to about 7 minutes.

First off all, Visual Studio enables parallel projects builds by default. As long as your projects don’t have dependency and can be built in parallel, it is possible to have up to 32 projects built at the same time. By default the number of projects built in parallel is set to the number of available cores. This option can be change from Tools > Options > Projects and Solutions > Build and Run.

vcbuild1

The VC++ compiler also supports multi-processor compilation. This can be enabled with a compiler switch, /MP. The result of this switch is that the compiler spawns into additional processes that simultaneous compile the source files. This option can be set per project from Project > Properties > Configuration Properties > C/C++ > General.

vcbuild2

This option is however incompatible with several compiler and language features. In case of incompatibility the behavior differs:

  • if the incompatibility is with another compiler option a warning is displayed and the /MP options is ignored
  • if the incompatibility is with a language feature an error is issued and compilation stops or continues depending on the error level

The incompatible features are: #import processor directive, /GM (enables incremental build), /Yc (writes a precompiled header file), /E and /EP (copies pre-processor output to the standard console), /showIncludes (writes a list of include files to the standard error console). These features are incompatible with /MP because they would imply shared resources (console or file) to be used possible at the same time from different processes.

The workaround for the #import processor directive is to move the directives to a file that is not built with the /MP option. However, in practice I had problems with that and the workaround that actually worked was to move the #import directives to the pre-compiled header. That is built (the corresponding source file actually) synchronously before the compilation of any other translation unit starts, so there are no incompatibilities. However, this has the drawback that any change in the imported type library will trigger a rebuild of the precompiled header and of the whole project.

vcbuild3

Below is a table of full build times for various VC++ projects from the same solution. The values were reported by MSBuild. The actual time values (shown to hundredths of a second) are not important (as they vary across builds and machines). What is important is the speed-up before enabling /MP and after it.

Before After
Release Debug Release Debug
00:00:58.43 00:00:53.14 00:00:37.32 00:00:25.23
00:00:45.41 00:00:33.25 00:00:12.16 00:00:07.47
00:00:56.85 00:00:54.90 00:00:51.35 00:00:50.72
00:00:45.41 00:00:44.34 00:00:24.41 00:00:20.16
00:01:00.92 00:01:05.97 00:01:15.40 00:01:14.07
00:04:17.89 00:04:25.88 00:01:34.66 00:01:17.97
00:02:59.41 00:02:26.42 00:01:11.99 00:00:54.07
00:00:46.96 00:00:43.96 00:00:17.70 00:00:17.41
00:00:46.05 00:00:46.05 00:00:16.69 00:00:14.40
00:01:44.84 00:01:35.11 00:00:30.29 00:00:28.13
00:00:11.02 00:00:14.26 00:00:07.97 00:00:08.24
00:00:09.28 00:00:12.75 00:00:07.59 00:00:07.59
00:00:08.46 00:00:08.95 00:00:10.89 00:00:08.25
00:00:18.68 00:00:19.79 00:00:14.32 00:00:11.35
00:02:46.37 00:00:50.76 00:01:52.16 00:00:28.88
00:00:13.87 00:00:11.66 00:00:09.24 00:00:11.06
00:01:05.37 00:00:54.76 00:00:25.45 00:00:26.83
00:00:29.00 00:00:16.68 00:00:13.32 00:00:12.94
00:00:25.79 00:00:14.05 00:00:12.09 00:00:12.04
00:00:22.80 00:00:11.50 00:00:11.60 00:00:11.93
00:00:31.76 00:00:39.33 00:00:25.00 00:00:21.19
00:00:56.01 00:00:59.04 00:00:26.62 00:00:23.95
00:00:20.40 00:00:12.92 00:00:08.64 00:00:07.99
00:00:16.48 00:00:18.61 00:00:09.18 00:00:08.71
00:00:09.74 00:00:10.49 00:00:06.84 00:00:09.84

Another strategy for increasing the build times is to reduce the number of times framework headers (STL, MFC, ATL, etc.) or headers from your project that do not change often and are included in many places are included into the various source files of your projects. These headers can be put in the pre-compilead header, and therefore compiled just once.

The next table shows the additional speed-up gain for several projects after moving framework headers to the pre-compiled header of each project.

Before After
Release Debug Release Debug
00:00:37.32 00:00:25.23 00:00:32.79 00:00:19.24
00:00:12.16 00:00:07.47 00:00:04.90 00:00:02.41
00:00:51.35 00:00:50.72 00:00:44.15 00:00:37.25
00:00:24.41 00:00:20.16 00:00:20.19 00:00:20.16
00:01:15.40 00:01:14.07 00:01:14.10 00:01:10.56
00:01:34.66 00:01:17.97 00:01:23.60 00:01:22.15
00:01:11.99 00:00:54.07 00:01:11.70 00:00:43.20

The speed-up gain by using these techniques may vary depending on your project specifics. In some cases the gain may not be significant or the build time may actually increase. You should play around with these settings and enable them only when they help you.

In summary, some of the strategies that can help increase your build times are:

  • break dependencies between projects they can be built in parallel
  • if you have large projects try to break them in smaller, independent projects; the more such projects you have the more work can be done in parallel
  • use multi-processor compilation (/MP) for C++ projects to enable compiling multiple translation units at the same time
  • move framework headers (STL, MFC, ATL, etc.) and other headers that do not change and are included all over your project to the pre-compiled header
  • use a SSD and keep your sources on it
, , , , , Hits for this post: 11395 .
Trackback

only 1 comment untill now

  1. Gravatar

    I still don’t get why in 2013 MSVC is so weak at parallel compilations (make -jX works for many years already).

    I mean, I still don’t get why /GM (enables incremental build) + /MP don’t work together. They are supposed to work together to bring down the compile times.

Add your comment now