Skip to content

Speed up C++ compilation, part 1: precompiled headers

How to speed up C++ compilation by using PCH (precompiled headers).

C++ compilation model

Working on a C++ program as a software developer means iterating over these steps in a cycle: write, compile, run (test), check results. A professional programmer should be interested in shortening each of those steps. While designing/writing code takes arguably most of the work, compilation time can become a significant factor in many cases. Sum up all this time throughout the day and you may be surprised. Even small changes in a handful of files can result in recompilation of large portions of a code base. This is largely because C++ inherits header files approach from C. When a header file changes, all of the files that include it have to be recompiled.

The basic unit of work for a C++ compiler is called a translation unit (TU). It’s a term (from the C++ standard) which, in most cases, refers to a C++ source file plus all of the files it includes and includes of those includes and so on and so on[1]. This means that the compiler is forced to preprocess and parse a header file each time it is included, separately for each TU. Obviously, there’s a room for improvement. Modules[2] proposal aims at a better model that can alleviate this pain. The C++ community had high hopes for modules in C++17 but we already know that’s not going to happen.

Currently, in absence of modules, the most common way to cope with this problem is to use a technique called “precompiled headers”. Nowadays, most C++ compilers support them in some way. In this article, I’ll focus on MSVC, GCC and Clang.

Precompiled headers test

If you haven’t heard of precompiled headers before, here’s a very short presentation of them. Let’s say we have a single source file that includes a quite “heavy” header:

// header.h
#ifndef HEADER_H
#define HEADER_H

#include <algorithm>
#include <deque>
#include <iostream>
#include <map>
#include <memory>
#include <set>
#include <thread>
#include <utility>
#include <vector>

#endif

// main.cpp
#include "header.h"

int main() {
  return 0;
}

This is how long it takes to compile main.cpp on my machine using GCC:

$ time g++ -std=c++11 main.cpp -o main                                                      
g++ -std=c++11 main.cpp -o main  0,47s user 0,08s system 95% cpu 0,570 total

Now, to enable PCH the only thing to do is… compile the header!

$ g++ -std=c++11 header.h
$ ls
header.h header.h.gch main.cpp
$ time g++ -std=c++11 main.cpp -o main                                                      
g++ -std=c++11 main.cpp -o main  0,08s user 0,03s system 95% cpu 0,117 total

GCC generated the header.h.gch file which is a PCH version of the header.h. Then, while compiling main.cpp, GCC notices the precompiled header and uses it instead of parsing the original header. The effect? 5 times faster compilation! OK, because main.cpp does hardly anything and header.h contains a couple of heavy headers from C++ standard library this example is a bit exaggerated. Or is it? How often do you have a cpp file that’s like 100 lines long and has, let’s say, 20 includes? I’d say that’s quite common.

Also, if you use the C++ standard library in your project it’s probably used in at least 90% of your source files. That’s thousands of lines of highly templated C++ code per header. Even if you don’t use the standard library there’s probably at least one library that provides general utilities to the rest of the project and it’s very likely that it also contains complex, templated code. Such libraries are perfect candidates for precompilation.

MSVC and stdafx.h

Microsoft’s C++ compiler has used stdafx.h[3] header convention for some time now. Its purpose is to contain “system include files and for project-specific include files that are used frequently but are changed infrequently“[4]. This is a very common approach to precompiled headers. If there are frequently used, rarely changed, expensive headers it’s enough to precompile them once and use that in subsequent builds.

MSVC model for precompiled headers.
MSVC model for precompiled headers.

In the diagram above stdafx.h includes standard C++ headers along with windows.h. Other source files include stdafx.h and benefit from precompiled headers. Stdafx.h must be included in every single source file. It can be done manually but it’s tedious and not really multi-platform friendly. Fortunately, MSVC comes with “Forced Included File” option that can force stdafx.h inclusion without modyfing a single file. Stdafx.cpp exists only because there must be at least one file with the “/Yc” (create precompiled header) switch. File containing precompiled headers is a result of compiling that file.

I won’t go into details on how to setup MSVC to use precompiled headers. There are plenty of tutorials on the internet, for example [5]. If you’re using CMake there’s at least one module that promises seamless addition of PCH in projects using MSVC/GCC/Clang compilers [6]. For other build systems, it should be easy to setup PCH since it’s a very popular technique.

GCC and Clang

It’s possible to mimic the MSVC model with GCC/Clang compilers. For multi-platform code it can be even the same stdafx.h but let’s call it precompile.h this time and use this simplified Makefile:

CXX = g++
CXXFLAGS = -std=c++11
OBJ = main.o a.o b.o

# file containing headers for precompilation 
PCH_SRC = precompile.h
# project headers that are going to get precompiled (pch dependencies)
PCH_HEADERS = header.h
# pch output filename
PCH_OUT = precompile.h.gch

main: $(OBJ) 
  $(CXX) $(CXXFLAGS) -o $@ $^

$(PCH_OUT): $(PCH_SRC) $(PCH_HEADERS)
  $(CXX) $(CXXFLAGS) -o $@ $<

%.o: %.cpp $(PCH_OUT)
  $(CXX) $(CXXFLAGS) -include $(PCH_SRC) -c -o $@ $<

If you’re not familiar with make syntax don’t worry, I’m not a make expert either and this is for presentation purposes only. Assuming that all required files are present, make output looks like this:

$ make
g++ -std=c++11 -o precompile.h.gch precompile.h 
g++ -std=c++11 -include precompile.h -c -o main.o main.cpp 
g++ -std=c++11 -include precompile.h -c -o a.o a.cpp 
g++ -std=c++11 -include precompile.h -c -o b.o b.cpp 
g++ -std=c++11 -o main main.o a.o b.o

First, the precompile.h header gets, well, precompiled. Then, all of the cpp files get compiled with the additional compiler flag -include precompile.h. This flag instructs the compiler to act as if precompile.h were included as a first header in a compiled source file.  So, for every file that gets compiled with this flag, the compiler searches for precompile.h.gch first before precompile.h in each search directory. The last compiler invocation combines object files into executable.

There’s a small difference between GCC and Clang here. Clang searches for precompiled headers only in the presence of  -include flag while GCC looks for them for each  #include directive. The GCC behavior could be used for separately precompiling project headers that change, let’s say, once a week and using more than one of those PCHs in one TU. Unfortunately, this is not possible, because there are some limitations…

Limitations and drawbacks

In my opinion, the biggest limitation with PCHs is the fact that only one PCH can be used per TU. Therefore, the only sensible way to use them is to gather the least frequent changing headers (usually standard C++ headers plus system headers) into one that’s going to be precompiled. Please take a look againa at the Makefile above. PCH there depends on header.h and all of the object files depend on PCH. As a result, changing header.h is going to be very costly:

$ make
make: 'main' is up to date. 
$ touch header.h 
$ make 
g++ -std=c++11 -o precompile.h.gch precompile.h 
g++ -std=c++11 -include precompile.h -c -o main.o main.cpp 
g++ -std=c++11 -include precompile.h -c -o a.o a.cpp 
g++ -std=c++11 -include precompile.h -c -o b.o b.cpp 
g++ -std=c++11 -o main main.o a.o b.o

Making changes in any of PCH headers means full rebuild. Even headers that change once a month can be problematic. The problem only grows with the team and the code base sizes. We need modules in C++ to solve it.

Other common limitations of PCH:

  • PCH header has to be first thing in TU (except for preprocessor directives).
  • PCH must be compiled with exact same compiler switches. Changing switches? Full rebuild.
  • Preprocessor directives used for compilation must be the same as for PCH or must not affect precompiled content.

Each compiler implements PCH differently so please check your compiler’s documentation for details [7].

Summary

Precompiled headers can help achieve a significant reduction of compilation time. Nowadays, most C++ compilers support PCHs in some way and there’s hardly a reason not to use them in most projects. There are limitations to their use but it’s the best thing before modules get their way into the C++ standard.

References and further reading

[1] Phases of translation

[2] [N4047] A Module System for C++

[3] Andrey Karpov – Stdafx.h

[4] MSDN – Precompiled Header Files

[5] A quick guide to using Precompiled Headers (Visual Studio)

[6] CMake Precompiled Headers Module

[7] GCC – Using Precompiled Headers


Header photo © ‘Tyne & Wear Archives & Museums’.

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *