Categories
Game Development

Improving compile and link speeds of C++ projects

At work we have a very large codebase, in the millions of lines of code. Nobody understands all the code, and half the people who wrote it aren’t at the company anymore. So a lot of my time is spent experimentally – tracing through code to figure out how it works, or making small changes […]

At work we have a very large codebase, in the millions of lines of code. Nobody understands all the code, and half the people who wrote it aren’t at the company anymore. So a lot of my time is spent experimentally – tracing through code to figure out how it works, or making small changes to fix unforeseen bugs. The problem is this requires a lot of recompiling small changes. Unfortunately, the code is designed in such a way that sometimes small changes result in the recompilation of hundreds of files.

I spend half my day not programming, nor thinking, but waiting for the compiler. This is by an order of magnitude the biggest hindrance towards my accomplishing anything meaningful. A single line of code change can take 15 minutes when you consider the time to build, the time to link, the time to regain my lost attention, and the time to do it all again for my second instance of Visual Studio (for network programming). Fortunately, I’ve learned a few things as the author of RakNet that can help with compile and link times. While they may be obvious to experienced programmers, even experienced programmers don’t consistently follow them so they are worth reviewing.

1. It’s bad to include header files in your cpp files. It’s exponentially bad to include header files in other header files

.cpp files increase the time to compile your project linearly. For example, if my cpp file includes “blah.h” then every time “blah.h” changes, then I have to recompile all cpp files that include that. That’s bad – but still far better than header files, which increase your total compile time exponentially. If files C.h and D.h include B.h, and B.h includes A.h, then anytime A.h changes we have to compile 4 header files – A.h, B.h, C.h, and D.h. But it’s not just 4 header files – we are now also rebuilding any cpp file that includes any of those header files.

2. Avoid, as much as possible, putting code in header files

Code is likely to change. Class definitions are much less likely to change. Furthermore, code in a cpp file is only included in that one place – that cpp. Code in a header file is included everywhere that header file is included. If you put code in a header file, that means that you are likely to change that header file and you are including the same code all over your program meaning it gets recompiled every time.

3. Split up your header into independent classes/structs/enums as much as possible.

It’s a hassle to add header files to a project. Oftentimes, when two kinds of interfaces, such as an enumeration and a structure, are both used with a class, it’s tempting to put both in the same header file.

Supermarket.h
enum Fruits {APPLE, ORANGE, GRAPE};
class Supermarket {Fruits GetType(Fruit *fruit);};

There are two problems with this. First, enumerations are likely to change. If we later add POMEGRANATE then every class that includes this needs to be recompiled. Secondly, every class that cares about Fruits now has to also include Supermarket, and vice-versa, resulting in unnecessary compiles.

4. If your class has complex types in the header, or is included by many other files, think about exposing an interface for that class.

Interfaces can be a hassle since you now have to maintain two copies of your class definition, rather than one. However, in some cases they can vastly cut down on compile times. Consider the following:

Database.h

#include "ComplexTemplatedList.h"
class Database
{
Rec* GetRecord();
ComplexTemplatedList recordsList;
};

DatabaseInterface.h

class Database
{
virtual Rec* GetRecord()=0;
};

In the second version, we got rid of all the implementation code yet every class that needs Database can still use it and now compile almost instantly.

Here’s another example:

class Logger
{
public:
void WriteLog(char *);
private:
char *logList;
int numLogs;
}

The logger will probably be included in a lot of places. If we were to add a field

int *logTimes;

then, even though the log times have nothing to do with the users of the log class, all those users will have to be recompiled. By using an interface, none of the member variables would be exposed, so adding or deleting member variables won’t cause recompiles of files that include this header.

5. Inside header files, include pointers to other classes rather than the classes themselves

CompileSlow.h

#include "CompositeClass.h"
class MyClass
{
CompositeClass a;
}

CompileFast.h

class CompositeClass;
class MyClass
{
CompositeClass *a;
}

Sorry if my last point is obvious, but it’s the easiest to do so is worth pointing out.

In conclusion, a few minutes of trouble today can save hours over the life of the project, not just for you but every other programmer that has to do builds.

2 replies on “Improving compile and link speeds of C++ projects”

Alternatively, buy Incredibuild. Our stuff was typically taking over an hour to build and we found a pretty linear decrease in the build time with each person that installed the Incredibuild client. In the end we got down to about 5mins for a clean build without having to change our code at all!

On *nix, there’s always distcc…

‘Course, with good code organization, that could be 2 minutes, instead of 5 🙂

Just yesterday, with incredibuild, I spent over 24 minutes building our level editor and game. More than 17 minutes of that was link time.

Leave a Reply to bill Cancel reply

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