Unity Build in KBibTeX
Nov. 10th, 2013 05:26 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Inspired by a blog posting on Improving build times of large Qt apps found through Planet KDE, I tried to apply the concept of unity builds to KBibTeX. The idea with a unity build is instead of compiling a set of source code files individually into objects before linking them to merge them into one big source file and only compile this file. This approach promises time and I/O savings for C++, as repetitive reads on included headers can be skipped. Some issues such as name clashes can occur in unity builds, though.
KBibTeX is a BibTeX editor for KDE. It is mostly written in C++, with the addition of some XML files and CMake scripts. The project contains of 110 C++ files, which concatenated have a length of about 32 900 lines (including empty lines and comments). For details on the code, please see the Ohloh analysis. The C++ files are used to build two binaries (the main program and one test application) and seven shared objects (.so files).
How the unity build concept can be used for a software project depends on the build system. As common to KDE programs, CMake is the default build system, followed by GNU make on Linux and jom or nmake on Windows. A quick search on the Internet for unity builds and CMake brought my to the page explaining how to reduce compilation time with unity builds. The description is not specific to KDE or Linux, but should work with any CMake-based C/C++ project.
To make use of unity builds in CMake, one has to copy and paste the function enable_unity_build from this blog posting into one's main CMakeLists.txt file (or the one in src/, depending on your directory structure). This function becomes available to all CMakeLists.txt files that are called recursively.
The function enable_unity_build has to be invoked for each link target, it works both for program binaries and shared libraries (i. e. essentially everything that is a result of linking .o files). For KBibTeX, I am using enable_unity_build right before kde4_add_executable (builds KDE-specific applications, similar to add_executable) and kde4_add_library (builds KDE-specific shared libraries, similar to add_library):
enable_unity_build(kbibtexpart kbibtexpart_SRCS) kde4_add_plugin( kbibtexpart ${kbibtexpart_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.h )
and
enable_unity_build(kbibtex${BINARY_POSTFIX} kbibtex_SRCS) kde4_add_executable( kbibtex${BINARY_POSTFIX} ${kbibtex_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.h )
Now, what is the benefit of using unity builds time-wise? To test this, I ran four setups: either with or without unity build and either single-threaded make (sequential) or double-threaded make (parallel, -j 2). The results look like this (make run through time on my older Intel Core2 Duo, 2.0 GHz):
Unity build | |||
with | without | ||
Build Parallelization | -j 1 | 95.54user 3.76system 1:41.10elapsed 98%CPU | 197.74user 8.91system 3:31.54elapsed 97%CPU |
-j 2 | 98.45user 3.73system 1:20.06elapsed 127%CPU | 209.47user 9.48system 2:03.98elapsed 176%CPU |
Comparing the sequential builds (no parallelization, using switch -j 1), using unity builds drastically speeds up the build time from about 207 CPU seconds (user+system) and 3:32 wall time at nearly 100% CPU usage down to 99 CPU seconds (user+system) and 1:41 wall time at nearly 100% CPU usage. Wow. Instead of compiling maybe 10, 20, or 30 C++ files before linking them, now only up to 5 source files (merged file plus some moc files) get compiled before a linking step.
In more realistic scenarios, you would try to make use of the multiple cores your CPU offers. As I have only two cores (not real ones, only hyper-threaded), used switch -j 2 for make. As expected, the classic non-unity build profits from this, as many compilations (but not the linking steps) can be parallelized: Time changes from about 207 CPU seconds (user+system) and 3:32 wall time at nearly 100% CPU usage to 219 CPU seconds (user+system) but only 2:04 wall time at 176% CPU usage (quite good parallelization).
For the unity build, less source files get compiled that could get parallelized and the non-parallelizable parts like linking start dominating the total time: Time changes from 99 CPU seconds (user+system) and 1:41 wall time at nearly 100% CPU usage to 102 CPU seconds (user+system) and 1:20 wall time at 127% CPU usage (remember Amdahl).
Now, this is a nice benefit for someone who wants to build a whole software project as quickly as possible. But what about a programmer who makes changes in a single C++ and rebuilds the project? Without unity builds, just the modified C++ source file has to be compiled and one link operation performed. With unity builds, all C++ files from the same ‘merge group’ of the modified source file have to be merged and this huge C++ file has to get compiled and linked. I haven't made experiments (yet), but I expect that it will take more time. The time penalty may be modest though, as during a code rebuild other I/O operations may get performed and those may dominate the total wall time for a rebuild.
As there are reasons to enable or disable unity builds, i. e. keep the classical approach, I patched KBibTeX's SVN trunk (revision 1804) to enable unity builds if option -DUNITY_BUILD=true is passed to cmake. If you omit this option or set it to false, the ‘conventional’ build will be run.