Currently viewing: GipsySoft » Front Page» Articles

Faster Compiles, Less Dependence

Slow Compiles, More Dependence

Why does my C++ project take so long to compile? Why is it when I change a lowly object or base class I somehow force a re-compile of the entire known universe. The answer to these questions is almost certainly one or more of the following:

Of course a lot of these ideas and opinions depend on the compiler and operating system you are using, I use Microsoft Visual C++ on Windows NT4.0, but, anything not related to machine or OS will affect any C++ compiler on any operating system.

By reducing header file includes, by reducing class dependencies and by breaking the project into smaller components you can greatly increase your turnaround from edit, through compile and into debug.

Header Files

If a header file A includes header file B then the compiler will have to check the date and time of the files and see if A needs rebuilding in relation to B. In any case both header files will have to be opened by the compiler, read into memory and parsed. And if file B contains include directives of it's own...

The fix is easy to implement, it's called an include guard and most header files already have them. The difference is that the include guard should also be placed where file B is included and not just within itself. By using include guards before we include the file we save the compiler the trouble of opening the file, parsing the include guard in the file and having to parse the entire file looking for the end of the include guard.

///////////////////////////////////////////////////////////Start
#ifndef FILEB_H
#define FILEB_H
//
//  File B declarations
//
#endif  //  FILEB_H
////////////////////////////////////////////////////////////End

///////////////////////////////////////////////////////////Start
#ifndef FILEA_H
#define FILEA_H
//
//  Only include file B if it is not already
#ifndef FILEB_H
  #include "FileB.h"
#endif  //  FILEB_H

//
//  File A declarations
//
#endif  //  FILEA_H
////////////////////////////////////////////////////////////End

MS Visual C++ makes use of a new (vendor specific) pragma that purportedly removes the need for doing this, I have no evidence that it either works or doesn't. The method described above is compiler independent and works even if you do not have the rights or desire to modify the header files you include.

The Microsoft compiler has one irritating habit in one of it's nicer features. When you create a new class by right clicking on the root of the class view it adds an include guard that looks like a GUID to the resultant header file - why does it do this? Surely the designer of this feature knows what a traditional include guard looks like and I am equally sure that my include guards do not need global uniqueness!

[TOP]

Class Dependencies

Class A needs class B for some reason and because of that, when you make a change to class B, everything that relies on it re-compiles. This takes some thought to get around. The first pass at getting out of this mess is to remove anything you can forward declare - pointer and references are prime candidates for removal.

//  MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H
#include "YourClass.h"

class CMyClass
{
public:
    explicit CMyClass( CYourClass & );
private:
    CYourClass *m_pYours;
};
#endif  //  MYCLASS_H

The above code is typical. The header file YourClass.h does not need to be included in MyClass.h , a better way is to forward declare the class CYourClass and remove the include directive as follows.

//  MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H
class CYourClass;
class CMyClass
{
public:
    explicit CMyClass( CYourClass & );
private:
    CYourClass *m_pYours;
};
#endif  //  MYCLASS_H

Now that YourClass.h is removed, clients of CMyClass should no longer need recompiling every time CYourClass changes.

Private members do not form part of a class interface, protected data members form as much of the class interface as public ones do, one aim is to remove private data and members altogether. The most widely used method is to gather your private data and members and to place them in a structure that is private to the class implementation. In the class declaration have a forward declaration of the type and in the private section of the class have a pointer to it.

...
private:
	class CPrivateData *m_ppriv;
...

In the constructor you should initialise m_ppriv to be a dynamically allocated instance of CPrivateData and in the destructor you should delete it.

CMyClass::CMyClass()
	: m_ppriv( new CPrivateData )
{}

CMyClass::~CMyClass()
{
	delete m_ppriv;
}

Private member functions can also be moved into CPrivateData, problems arise when a private function calls a virtual function and if the private function has been moved to a different class there is no linkage. There are ways and means of overcoming this problem but if you need to know more on this subject I strongly recommend you read Large Scale C++ Software Design.

[TOP]

Huge Project

Huge project, hundreds of thousands of lines of code. Try dividing the project into two distinct areas, those that change and those that don't. Those parts that do not change should be moved to some sort of library.

The libraries should then be divided up into those bits that are not used in the product very often and those that are. The library code that is used often would be suitable for either statically linked libraries or statically linked DLLs. The library code that the project uses least often should be dynamically loaded by your project. This will speed your link times and will also have a positive effect on your application load speed.

In MSVC (I am sure this is true of many compilers) there is the ability to use pre-compiled header files, we did tests with this feature. We had automatic use of pre-compiled headers in a project of about 200,000 lines of code, the pre-compiled header did indeed speed compilation but the real speed improvement came when we specified the PCH file settings and picked out those source files that couldn't use it. The speed increase was dramatic, from about 28 minutes down to something like 10. Don't use the automatic pre-compiled header option.

The latest versions of MSVC also have the ability to contain subprojects and to have a dependency between them, only add sub-projects you really need. By having lots of sub-projects you are potentially adding an enormous amount of code to the dependency checking required to do a compile.

When you are debugging a DLL if you set the DLL as the active project and set your project EXE as the target you may find that the debugger starts quicker.

Some DLLs do not have to be built as debug, there are some cases where heap objects are not passed across the DLL boundaries and so there is no direct need to have the DLL as debug. Not having the debug information can be a problem but if the DLL loads executes quicker then it is sometimes worth the loss. DebugHlp is a good example of this, compiled debug or release it has the same functionality. The only time DebugHlp needs to be compiled for debug is if there is a bug in it.

[TOP]

Slow Computers!

In the end you will probably be forced into buying a more powerful computer, if possible nag your boss to get decent kit, it is a false economy to buy cheap machines with IDE drives and low resolution monitors. They will drive you mad with frustration and blind with the low resolution and low refresh frequency.

Multi-processor machines with SCSI hard drives are getting cheaper and should be well within reach of even the smallest software house. We have tested various configurations over the years and have found that the hard drive is the compiler's bottleneck, get the best you can afford, get two if possible - one for the OS, tools etc. and the other for just your projects and your compiler output.

SMP is next best, if you don't want to be twiddling your thumbs whilst the compiler spends time number crunching SMP is the only way to go. Microsoft Developer Studio up to version 6.0 does not perform any sort of multi-processor magic but having a second processor allows you to continue editing code in the mean time. I am fairly sure that SMP helps even a single threaded compiler due to the fact that the other processor can still perform the house keeping. SMP will not be used with Windows95/98 due to the fact that there is no support for it in the OS.

RAM is good up to a point, we found that going from 32MB to 64MB made a big difference, 64MB to 96MB was almost as good, going to 128 made very little difference. That said we only buy developer machines with 128MB as standard and I have had 256MB at home for well over two years so I do believe it's worth having.

[TOP]