CMake has an option, CMAKE_UNITY_BUILD, to automatically turn your builds into unity-builds, which is essentially combining multiple source files into one. This is supposed to make your builds more efficient. You can just enable enable it while executing the configuration step of your CMake builds, so it is really easy to test. It might just work without any problems. Here are some examples with actual numbers of what that does with build times.
Project A
Let us first start with a relatively small project. It is a real project we have been developing, that reads sensor data, transports it over the network and displays it using SDL and Dear ImGui. I’m compiling it with Visual Studio (v17.13.6) in CMake folder mode, using build insights to track the actual time used. For each configuration, I’m doing a clean rebuild 3 times. The steps are the number of build statements that ninja runs.
| Unity Build | #Steps | Time 1 | Time 2 | Time 3 |
|---|---|---|---|---|
| OFF | 40 | 13.3s | 13.4s | 13.6s |
| ON | 28 | 10.9s | 10.7s | 9.7s |
That’s a nice, but not massive, speedup of 124,3% for the median times.
Project A*
Project A has a relatively high number of non-compile steps: 1 step is code generation, 6 steps are static library linking, and 7 steps are executable linking. That’s a total of 14 non-compile steps, which are not directly affected by switching to unity builds. 5 of the executables in Project A are non-essential, basically little test programs. So in an effort to decrease the relative number of non-compile steps, I disabled those for the next test. Each of those also came with an additional source file, so the total number of steps decreased by 10. This really only decreased the relative amount of non-compile steps from 35% to 30%, but the numbers changes quite a bit:
| Unity Build | #Steps | Time 1 | Time 2 | Time 3 |
|---|---|---|---|---|
| OFF | 30 | 9.9s | 10.0s | 9.7s |
| ON | 18 | 9.0s | 8.8s | 9.1s |
Now the speedup for the median times was only 110%.
Project B
Project B is another real project, but much bigger than Project A, and much slower to compile. It’s a hardware orchestration system with a web interface. As the project size increases, the chance for something breaking when enabling unity builds also increases. In no particular order:
- Include guards really have to be there, even if that particular header was not previously included multiple times
- Object files will get a lot bigger, requiring /bigobj to be enabled
- Globally scoped symbols will name-clash across files. This is especially true for static globals or things in unnamed namespaces, which basically don’t do their job anymore. More subtly, things moved into the global namespace will also clash, such as the classes with the same name moved into the global namespace via
using namespace.
In general, that last point will require the most work to resolve. If all fails, you can disable unity build on a target via set_target_properties(the_target PROPERTIES UNITY_BUILD OFF) or even just skip specific files for unity build inclusion via SKIP_UNITY_BUILD_INCLUSION. In Project B, I only had to do this for files generated by CMakeRC. Here are the results:
| Unity Build | #Steps | Time 1 | Time 2 | Time 3 |
|---|---|---|---|---|
| OFF | 416 | 279.4s | 279.3s | 284,0s |
| ON | 118 | 73.2s | 76.6s | 74.5s |
That’s a massive speedup of 375%, just for enabling a build-time switch.
When to use this
Once your project has a certain size, I’d say definitely use this on your CI pipeline, especially if you’re not doing incremental builds. It’s not just time, but also energy saved. And faster feedback cycles are always great. Enabling it on developer machines is another matter: it can be quite confusing when the files you’re editing do not correspond to what the build system is building. Also, developers usually do more incremental builds where the advantages are not as high. I’ve also used hybrid approaches where I enable unity builds only for code that doesn’t change that often, and I’m quite satisfied with that. Definitely add an option to turn that off for debugging though. Have you had similar experiences with unity builds? Do tell!