Optimising compile times with precompiled headers
Inside niven, I’ve been using precompiled headers since the first version. Read on for some insights I gained over the years.
Platform
The following is written while I was using the Microsoft Visual C++ 8.0 compiler. It is probably also true for earlier versions, but I cannot check that.
Starting point
niven had precompiled headers right from the beginning. They used to contain often used internal headers and large parts of the STL (map
,vector
, etc.). Over the time, a lot of headers from Boost found their way into the precompiled header. The last version I had compiled to a 47 MiB large .pch
file. Compilation time was roughly one minute for the core library, which was already pretty good given the size and complexity of the source code. I’ve been using the best practices for physically decoupling the code. This means each header forward declared as much as possible and includes were delayed so most of the happened in the .cpp
file (where they should be). In addition, often used headers were put into a single precompiled header and compiled just once for the application.
The Fast & the Curious
Well, as always, fast was not fast enough for me (1 minute for a full recompile on my notebook - as if I would have a spare minute :) ). I’ve taken a deep look at what should really go into the precompiled header, commenting everything out first. Step by step, I could strip down approximately half of my precompiled header, reducing the compile time down to 37 seconds - now my notebook is just as fast as my desktop previously. The .pch
file is now 30 MiB - a 33% reduction.
Lessons learned
The most expensive headers are those taking huge parts of the STL and other template magic with them. For example, including <boost/bind.hpp>
can easily double the compile time per source file! Compared to this, files with a lot of preprocessor magic (in my case, <boost/signals.hpp>
) don’t make a visible difference. I’ve moved them out and back of the precompiled header, but I could not measure any improvement - seems the preprocessor is really fast. Also, be aware that some headers tend to include stuff you don’t think about usually. For example, <boost/signals.hpp>
includes <list>
- after removing the former from the precompiled header, some parts of my code complained about not finding the latter.
An interesting result of this is that eventually, the compiler is able to process a lot of files per second now. You don’t see individual files popping up in the output window but batches of three or even more files - woohoo! Link time and precompiled header generation is now dominating the build time. I presume most of it is spent in the interprocedural optimization step, as the core library is linking basically against the bare minimum you have to link (C-Runtime, C++-Runtime, some Windows specific libraries). As soon as I get a chance, I’ll try with the Intel C++ 9.1 Compiler to see how big the difference is over there.