C++: Fun With Precompiled Headers
Those of you who have worked on middle-sized to large projects in C++ (any more than 50000 lines of code) know that build time becomes a rather important factor in the development process. On any typical project, a full build time can very well exceed 10 minutes on a modern machine. Although there are many factors aside from mere line count (for example, heavy use of templates) affecting the exact build time, any amount of time more than a few seconds you spend staring at your monitor waiting for a build to finish is hardly pleasant.
On really large projects, some very drastic measures are taken to mitigate the problem of build times. These may include distributed (even grid-based) build systems or dedicated build machines that automatically build the project after each code update to the project repository or at designated intervals. But for any smallish company or team, such measures may not be applicable. Using Pre-Compiled Headers (or PCH for short) can be an attractive solution.
First let me go over some basic stuff. As you know, a C++ compiler only accepts individual C++ code files as input (called a compiland (like operand!) sometimes.) Your project might easily have a couple of hundred (to a few thousand) source files (not counting the header files) but each must be fed through the compiler to produce an object file individually. The next step would be to run a linker (or alternately a “librarian”) to link those object files together and produce an executable (or a library) file.
We all know that all the “.cpp” (or “.cc” or whatever) files are compiled separately. The obvious result is that the header files that each source file includes must be read and parsed and incorporated into the object file once for each and every source file. This opens up an opportunity for greatly saving on compile time. You see, a typical source code file is usually between 200 to 2000 lines of code itself, but it very often includes tens of thousands of other code in form of included header files. It’s really easy to see for yourself. Just pass your source code file that includes a handful of STL headers to the compiler and tell it only to “preprocess” the code (check your compiler’s documentation.) Look at the resultant file. That’s what the poor compiler receives for each cimpiland.
If you use template classes and functions heavily, the situation becomes even more difficult. Because each template class will be specialized by the compiler for each usage throughout the code. That is, you write a 500-line template class, and use it with 20 different types as template arguments. That’s 10000 lines of code right there, not to mention the fact that the source is now much more complex for the compiler to optimize and generate target code from. Let’s face it, templates are great facilities for generic programming, cleaner design, better performance and general wizardry, but they’re no friend of the compiler’s!
Anyway, suppose that you include <map>, <fstream> and <algorithm> headers (from STL) in almost all of your source code files. Since each file is passed to a separate instance of the compiler, no information is interchanged between the instances and the compiler reads, parses, generates code from and optimizes all the three header files for each source file. This is clearly superfluous. The opportunity I mentioned is right here. If you could somehow do the many steps necessary for processing these files (or at least some of the steps) and store the results and the state of the compiler somewhere (in a file, for example) you wouldn’t have to do that for every source file. You would just read that intermediate state file almost directly into memory and save a lot of time.
This is precisely what using pre-compiled headers is. There are some restrictions however. The headers you want to compile into a PCH file have to be exactly the same and they have to be included in every source file and in exactly the same order. This might mean that some of your source files may include headers that they don’t actually need, but this seldom causes any problem. Also, it is essential that the headers you want to pre-compile be included as exactly the first statements of each source file. Because C++ code (specially preprocessor code) can affect the way other included header files (that are included further in the file) behave, there must not be any code before the inclusion of the headers intended for pre-compilation or that code too, becomes part of the PCH.
One more thing that is necessary is to tell the compiler to stop putting headers into the PCH at some point. This is achieved by putting a “#pragma hdrstop” after all the headers that we intend to be included in the PCH file, and before anything else.
There’s a convenient way to manage all this. We can put all the inclusion statements of all the include files we want to go into the PCH file, along with the pragma directive in a specifically designated header file (let’s call it “PCH.h“) and include this file at the very top of each and every source file (remember: only source files, not other header files.) This ensures that the header files and their order is exactly consistent across all files and removes the need for much duplication of #include statements that might lead to much grief. (As a side note: Don’t Repeat Yourself!)
Now, you tell the compiler to create the PCH, and use it throughout the build. Therefore, you have to change the compile command for exactly one of your source files to create the PCH while it’s being compiled (and put this command at the beginning of the list of files to be compiled in your Makefile or whatever) and change the compile command of all the other source files to only use this newly created PCH file.
Since the precompiled header can become a very big file (many tens or even hundreds of mebi-bytes) and it has to be rebuilt every time either the headers included in it are changed (a genuine requirement) or the source file used to create it changes (imposed, because the code in that file will not be included in the PCH anyway) we can create a dedicated source file just for the purpose of PCH creation. This (e.g.) “PCH.cpp file will be empty except for a single #include <PCH.h> statement. This way, this source file never changes and one cause (however minor) for rebuilding of PCH is eliminated.
For those of you who use IDEs and Microsoft Visual Studio in particular (like myself) here’s how you do it in the VS IDE. First go on and create the said “PCH.h” and “PCH.cpp” files above. Remember to include “PCH.h” in every source file at the very top and remember to add “PCH.cpp” to your project. Also very important is that you must remove all the inclusion statements of the files already included in the PCH from each and every source and header file in your project (it’s best to #define them out (actually #if defined them out,) in case this didn’t work out for you and you wanted them back!
OK. Now you have to tell VS to tell VC that you’re going to use PCH. Go to Project->Properties and in that dialog (while making sure “All Configurations” is selected in the “Configuration” combo box on the top left) Configuration Properties->C/C++->Precompiled Headers. Change the value of “Create/Use Precompiled Header” to “Use…” (with command-line switch of “/Yu”) and change the file name in “Create/Use PCH Through File” to whatever the name of your header is (“PCH.h” in our example) and don’t forget any path prefix it might have (you shouldn’t give an absolute path here, but a path relative to one of your include search path directories or your source file directory.)
This has the effect of setting the compile commands for all your source files to use PCH. But someone has to create it first! Find (in “Solution Explorer” window,) the source file you designated to create the PCH (for example “PCH.cpp”) and (right-click on it and) open it’s “Properties” dialog. While again making sure “All Configurations” is selected (as opposed to just “Debug” or “Release” or whatever), find your way to the same place as before (PCH settings.) These are the build options just for our selected file. Only change the “Create/Use Precompiled Header” to “Create…” (instead of “Use…”) and your all set. You should not change the name of the files (neither the header file nor the resulting PCH file) in this dialog.
Now make a full rebuild of the project. If you’ve done it right, and I haven’t forgotten anything important, and your project is big enough, with this rebuild you should see a great speed up. I have seen build times go from 20-25 minutes to 5 minutes and from 8-9 minutes to 1:50-2 minutes instantly.
I suggest you time your builds before and after the use of PCH (in VS2005, go to Tools, Options…, Projects and Solutions, VC++ Project Settings, Build Timing and set it to “Yes”.)
One thing to note. Sometimes, if your project is large enough, the VC compiler will run out of memory and terminate while building the PCH. In that case, you should append a “/ZmXXX” to all your compile commands, where XXX is the amount of memory in some weird scale and unit. Check the compiler documentation (or follow the build error you get when you run into this problem.) In order to do this inside the IDE, go to you project properties dialog, make sure “All Configurations” is selected, go to “C/C++”, “Command Line” and add the (e.g.) “Zm150″ to the (probably) empty box of “Additional Options”. Experiment with the value a bit to find a suitable value that doesn’t exhaust all your available memory and still allows a build.
I hope this mini-guide comes handy to someone someday! Please let me know of any errors I’ve made, or any omissions or inaccuracies.
Thank you for this useful article. Putting an abstact for such a long post will help the readers to undrstand it better.
Thanks for your suggestion, but for several reasons, First, I don’t believe these ~10000 characters constitute a “long” article. Second, after you read the title, you’re either interested or not, and if you are interested, then why not read it? Third, the first two paragraphs serve as a kind of introduction, or abstract. Fourth, I’m not writing an academic paper. This is a blog post and it happens to be on my own blog. It’s not my style to go too much out of my way to make it easy for humans (other than myself) to read what I write. No one needs to read it, it’s not essential or crucial in any way. If you have better things to do, by all means go and see to them!
@mojtaba:
Hi, by the way!
This page has been loaded on my Firefox for days now, hoping to get a glance of my eyes. I just don’t feel like reading it. Maybe I’m becoming to old for these.
Yeah, “all your base are belong to us”.
–
Caffeine can seriously damage the cortex tissues. Believe me!
That just reminds me of once I was trying to solve a problem. It was one of IAUMCCCs (I suppose). I solved the problem and implemented it, but it took a lot of time to compile and run (about one minute!) At first I thought there’s something wrong with the algorithm and tried to find the optimum solution, but without success.
Later I found your implementation of that problem, using the same algorithm as mine but nevertheless it worked properly and compiled in a few seconds! The only difference was that I had included and headers in my source code and you hadn’t. For the first time I realized using STL headers may affect the compilation time.
BTW, why is that? That project was not a large one at all; actually it was rather a small project (about 30 lines or less).
There seems to be two words missing from your post, and those two are the names of the header files you mention!
I’m guessing you enclosed the names in ‘<’ and ‘>’ and my blog software (WordPress) ate them, in an attempt to interpret them as HTML! I’m pretty certain that you already know this, but you can (probably should) use “<” to represent ‘<’ and “>” for ‘>’ in an HTML environment.
Now, about this compile and run time matter. Do you still have your code? What compiler you were using? Older compilers used to be faster in some areas (due mostly to the simpler, and non-standard, implementation of their STL) and slower in others. The behavior of C++ code is one of my interests and I’m curious to find out more about this problem you had.
Ok, here it is!
//smart
#pragma warning (disable:4018)
#pragma warning (disable:4786)
#include < fstream >
#include < vector >
#include < string >
#include < iostream >
#include < map >
#include < cmath >
#include < sstream >
#include < algorithm >
using namespace std;
struct p
{
long first, second;
}points[ 1000 ];
void main()
{
ifstream in(“g.in”);
int pn;
in > > pn;
while( pn != 0 )
{
for( int i = 0; i < pn; i++ )
in > > points[ i ].first > > points[ i ].second;
long int sum = 0;
for(int i = 0; i < pn – 2; i++ )
for( int j = i + 1; j < pn – 1; j++ )
{
int s = 2;
long int Dx = points[ i ].first – points[ j ].first;
long int Dy = points[ i ].second – points[ j ].second;
for( int k = j + 1; k < pn; k ++ )
if( Dx * ( points[ k ].second – points[ i ].second ) ==
Dy * ( points[ k ].first – points[ i ].first )
)
s++;
if( s > sum )
sum = s;
}
if( sum > = 4 )
cout < < sum < < endl;
else
cout < < 0 < < endl;
in > > pn;
}
}
//===========================================================
#include < iostream >
#include < fstream >
using namespace std;
const int SIZE = 1000;
struct point {
int x, y;
} points[SIZE];
int npoints;
int main()
{
ifstream fin(“g.in”);
int i, j, k, icase=0;
fin > > npoints;
while (npoints != 0) {
icase++;
for(i=0; i < npoints; i++) {
fin > > points[i].x > > points[i].y;
}
int max = 0;
for(i=0; i < npoints-2; i++) {
for(j=i+1; j < npoints-1; j++) {
// determine eqn ax+by=c
int a = points[i].y – points[j].y;
int b = points[j].x – points[i].x;
int c = points[j].x*points[i].y – points[j].y*points[i].x;
int count = 2;
for(k=j+1; k < npoints; k++) {
if (a*points[k].x + b*points[k].y == c) {
count++;
}
}
if (count > max)
max = count;
}
}
if (max > = 4)
cout < < “Photo ” < < icase < < “: ” < < max < < ” points eliminated” < < endl;
else
cout < < “Photo ” < < icase < < “: 0 points eliminated” < < endl;
fin > > npoints;
}
return 0;
}
Phew!
However after four attempts just to make a correct comment, I really feel I’m loosing interest in the problem, itself!
Very nice article.Answered one of my long time wonders. Thanks.