Precompiled headers

Our project just takes too long to compile. It’s only a few hundred thousand lines of code, but it compiles like there are millions.

I’ve spent most of last night and today reorganizing header files and project files and turning our primary include file set into a single, precompilable header.

Visual Studio, dramatic effect: just over 2 minutes 27 seconds off the time to build the entire project while also adding our network API to the build! Xcode seems about the same or slower (I’ll benchmark that next week) and I got tired of wrestling with autoconf/automake under Linux for the host.

Much of the slowness seems to be namespace wastage and header bloat: we have some, uhm, interesting conventions that have never quite gone away.

When our project started, C++ was still nascent and frowned upon, so C++ stuff was heavily avoided until I started pushing it about 5 years ago and it didn’t start infecting the client for about another year or so…

As a result, our early coders had a love for, what now seem ersatz, conventions like this:

typedef struct tagSTRUCTNAME
{
  …
} STRUCTNAME, *LPSTRUCTNAME;

The tag bit is an ancient C throwback, the *LPSTRUCTNAME is a convenience hack which is apparently supposed to make pointers clearer and let you  declare multiple pointers on a single line more easily:

STRUCTNAME *p1, *p2 ; // Normal style
LPSTRUCTNAME p1, p2 ; // LP style

 

But what it has the upshot of doing is meaning that every time we have a function-call prototypes header, it winds up #including the header with the type definitions. Instead of

extern void piGetSurface(…, struct piSURFACEDESC* surface, …) ;

We have

#include “ldtex.h” // Include 3000 lines of headers

extern void piGetSurface(…, piLPSURFACEDESC surface, …) ;

This header file gets included in 21 cpp files and 9 header files. Only 2 of the CPP files actually care about the definition of piSURFACEDESC. The rest only need to know it’s some kind of structure that can be pointed to. 71 source files process the full 3000 lines of ldtex.h when all they need is one of the dozen or so function prototypes in a header file that includes it.

Additionally, every instance of this tripple-naming method of declaring a struct results in 3 entries in the namespace for one structure which definitely doesn’t help the compilers performance.

Then there are our copious named enums. There doesn’t seem to be a good way of doing a prototype with a named enum:

typedef enum PIZZA_TYPE =
{
  VEGGIE
, PEPPERONI
, CLUB
, …
} ;

extern void someFunction(PIZZA_TYPE, …) ;

You can’t do “extern void someFunction(enum PIZZA_TYPE, …)” in the forward declaration of someFunction, which means you have to include the enum declaration. And we have a lot of enum declarations. This significantly clutters up our namespace and header files. The “clean” way to do it would add hundreds, if not thousands, of new header files and create a maintenance nightmare.

(Before you suggest we not name our enum types, the point is to ensure strong typing)

Well, no matter. Significantly reducing the compile time of the Windows project is good. It probably gets the most builds and the host build time is already some 5 minutes faster than it was a few years ago. I’d have liked to have seen some improvements in XCode/GCC build times but I’ll come back to that in the future when I have time to fight the Gnu tools.

The meat of it was easy enough to set up; I wrote some scripts to remove the 3 main #include culprits (syscfg.h, platform.h and systypes.h) from all the header and CPP files, I reorganized the 3 headers into one platform-specific header and two common headers; then I used Visual Studio’s “forced include” / XCodes “prefix header” / GCC’s “-include” argument, and voila. Everything #include’s syscfg.h.

Then, under Visual Studio, I made syscfg.h the precompiled header for everything but the couple of .cpp files that need to not include it and voila.

I had tried, at first, doing

#include PN_SYSCFG_H

and making the platform’s project provide PN_SYSCFG_H=”folder/syscfg.h” but Windows didn’t like that. Oddly, if you do

#define PN_SYSCFG_H “folder/syscfg.h”
#include PN_SYSCFG_H

But if you define it thru the project settings instead, you get an error:

error C2006: ‘#include’ : “expected a filename, found ‘identifier’ defined file name” fatal error C1083: Cannot open include file: ”: No such file or directory

Infact, if you do both – the #define and the project setting – you get an error about redefinition of “PN_SYSCFG_H” citing “command line” as the previous definition, and then you get the C2006 error. Meh.

2 Comments

Uggg…C code. I’ve never met a group who so liked to make their own life harder as do C coders.

Incidentally, you spent over a day to shave 2.5 minutes off compile time? How long did it take before? How many times a day do you have to compile the code?

As for the overly large headers, I have a solution that may work. I once worked on a project where we were porting a library from C++ to C and due to the loss of namespaces there was…uncleanliness.

My boss was determined not to pull the header files into separate files(I still think that’s cleaner) so I ended up grouping things together within the header file using #ifdef’s. Then the .cpp file would define only the functions/enums it was interested in.


//header.h
#ifdef HEADER_H_GRP_1
void func1(...);
void func2(...);
#endif
#ifdef HEADER_H_GRP_2
void func3(...);
void func4(...);
#endif

You could also redefine func1 as long as you put a test to make sure GRP_1 and GRP_2 were mutually exclusive.

And then in the .cpp you would have this:

#define HEADER_H_GRP_2
#include "header.h"
....

To be fair, I was still a little on the fence myself about C++ in 1999 when they broke the first code for WWIIOL. C++ has come a enormously long way since then.

Really, the strongly-typed enum defs are our bane, all for the want of the ability to say

extern void someFunc(enum foo, int i);

If you didn’t have to #include the file with the enum definition, we could clean up our headers in a day or two.

Discovered today that I was precompiling the precompiled header for every file in the build; fixed it so that it only creates the PCH for one .cpp file and the rest use that file and Martini was impressed with how fast it built after that.

Leave a Reply

Name and email address are required. Your email address will not be published.

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

You may use these HTML tags and attributes:

<a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <pre> <q cite=""> <s> <strike> <strong> 

%d bloggers like this: