This chapter describes Smart Build, a feature that allows the Delta/C++ compiler DCC to automatically determine the nature of changes in header files between compile runs, and to recompile only what actually needs to be recompiled, according to the Delta-compatibility rules.
Smart Build contains two interdependent mechanisms: precompiled headers (available in DCC and CC), and Smart Build itself, which uses these headers to calculate the differences between two versions of a header file.
This chapter contains the following sections:
The Smart Build facility in the Delta/C++ compiler allows the compiler to automatically determine the nature of changes to a header file, and recompile only a compilation unit (that is, generate object code for it) if necessary. (See the definition of Delta-compatible changes in the previous chapters.)
Smart Build allows you to continue using your current build mechanisms of choice (like make(1)), and still take advantage of Delta/C++'s benefits of reduced recompilation. For example, make, on noticing that a header file has changed, attempts to launch a recompile of every compilation unit that includes that header file. Smart Build allows make to examine the exact nature of the change, and generate new object code only when it is necessary.
When the change is a Delta-compatible one, this allows the compiler to terminate the compile for most compilation units at the early stages (typically 1-2 seconds) and merely touch the object file, thus dramatically speeding up the recompile.
To do this, the compiler builds binary precompiled files for each of the header files used in the compilation—one for each source file. When a file is modified, the compiler rebuilds the binary precompiled file, and can compare it for differences against the previous version of the binary file.
Smart Build has two interdependent mechanisms: precompiled headers, which are available both in DCC and NCC, and the Smart Build facility, which uses these precompiled headers to calculate the differences between two versions of a header file.
Smart Build can be used with both NCC and DCC. Smart Build skips rebuilding more object files in DCC, but using it with NCC still gives you a performance increase.
Invoke the Smart Build facility by adding the -smart option to the DCC command line:
DCC -smart[,repository_dir] [options] . . . file . . . |
repository_dir is the directory where the compiler keeps the precompiled binary versions of the header files. The default is ./ILDUMPS. If the specified directory does not exist, the compiler will attempt to create it with mkdir(2).
When a header file is touched in any way and a rebuild is performed—usually using make(1)—the first invocation of the compiler (for the first compilation unit recompiled by make(1)) performs the following functions:
notices the modified header file,
rebuilds the binary precompiled file
computes the differences between this version of the binary file and the previous version.
If any changes are not Delta-compatible, the compiler will generate new object code for the compilation unit, otherwise it prints a message:
Remark(2): Smart Build: recompilation skipped |
The compiler then touches the object file (so that make will not attempt to repeatedly recompile the file in future invocations).
If you invoke the compiler in the same run of make(1) for the remaining compilation units that also include this modified file, the compiler will
see the new version of the binary precompiled file
notice that the file has been rebuilt since the last time the object file for this compilation unit was created
examine the recorded changes from the previous version of the header file
see if any of the changes affect this compilation unit
If not, the compiler quickly prints the above message and touches the object file, so that the compiler will generate new object file for this compilation unit, too.
The greatest benefits of Smart Build are available with DCC, where the compiler has a greater leeway to skip rebuilding object files if changes are deemed to be Delta-compatible. However, it also benefits NCC to a somewhat lesser extent.
When using Smart Build with NCC, most of the rules for compatible changes do not apply. For instance, adding or reordering members in a class causes any compilation unit using that class to get its object file rebuilt.
There is a small set of changes that are considered compatible:
You may reformat or add comments without changing the order of the declarations in a file.
You may add an extern variable or function declaration.
You may add a type declaration.
If the compiler determines that the changes meet these criteria, it will skip recompiling the compilation units that include that object file.
The precompiled header file mechanism used in the Smart Build facility has some major advantages:
It is fully automatic.
The pre-compilation is done on a per-header file basis, so the users need not modify their source in order to use this mechanism.
The mechanism is extremely sensitive to external factors like macro definitions, especially if certain macros have different settings for different compilation units, including the same header file.
There are circumstances in which the compiler has to rebuild a header file (or use the source header file) even if it has not changed:
You use a macro in the header file with a different setting than when it was last precompiled.
The compiler detects a condition due to which it cannot build a precompiled header file (usually an implementation compromise — see “Conditions for Not Building Precompiled Headers”).
There is an explicit directive in the header file that tells the compiler not to precompile the header file.
Even if you have to rebuild object files (either if the changes are not compatible, or object files are missing), the precompiled header mechanism still provides a significant performance increase.
If you don't have to rebuild precompiled headers for any other reasons (see above), then you can expect to see about a 20% improvement in compilation time.
Most of these inefficiencies are due to macro dependencies. For example, consider the code segment in Example 5-1.
#ifndef _SIZE_T_DEFINED #define _SIZE_T_DEFINED 1 typedef unsigned int size_t; #endif |
Code such as this frequently appears in many of the system header files. (For instance, you may have several files that need the definition of size_t, but you do not necessarily want to include the whole of each others' contents just to get one definition.)
Inefficiencies occur if two files (for example, stdio.h and stdlib.h) satisfy the following requirements:
Both files include the same code segment.
Both files are included in the given order in one compilation unit, and in the opposite order in another.
This kind of situation creates the problems listed below.
In the first compilation unit, the precompiled header files for stdio.h is built on the condition that _SIZE_T_DEFINED was undefined on entry (since the generated precompiled header should contain the definitions of _SIZE_T_DEFINED and size_t).
The precompiled file for stdlib.h is built on the pre-condition that the macro was defined, and equal to the string “1” (so that the precompiled file has definitions for neither of the two identifiers).
When the second compilation unit first includes stdlib.h, it notices that _SIZE_T_DEFINED is undefined. The unit must then rebuild the precompiled file for stdlib.h so that it can include the definitions for the two identifiers (it has no idea that an include of stdio.h is forthcoming, as indeed it may not). Since it must not contain the definitions of the two identifiers, the precompiled header file for stdio.h (which follows) must also be rebuilt.
To avoid this, you should put any such declarations in their own header files (for example, a common one for such common declarations). You should protect these declarations against multiple includes. Each header file that needs a definition of any such common identifier should include this header file.
You must try to minimize the use of #ifs and #ifdefs in header files, except for the outermost multiple-include protection. This is especially important if they are going to be given different values for different compilation units.
Under some conditions, the compiler cannot build a precompiled header file. It builds a stub precompiled header file that records time-stamps and other header information. If such a file is modified or even merely touched, the compiler generates new object code for any compilation unit that includes such a header file.
The factors that prevent the compiler from building a precompiled header file are:
The include file does not begin or end at file scope. For example, if a file is #included inside a function. This is a problem, since both the file being included and the file doing the #include are marked as “nonprecompilable.”
If you use templates and exceptions. This release does not implement the mechanisms to represent templates and exception structures in a precompiled header file. If a header file uses or defines a template (that is, a mere direct mention of a template class or function), no precompiled file is created for that header file. However, a reference to an externally defined typedef that expands to a template class instance is permissible.
If you have a function with a default argument declared in one header file and defined in another header file. The second header file is marked as nonprecompilable.
If you have a class in one header and in-line constructor or destructor body in another. The second header file is marked as nonprecompilable.
If you have a static data member declared in one header file and defined in another. The second header file is marked as nonprecompilable.
If you have an access-adjustment declaration that refers to another access-adjustment declaration in a different header file. The second header file is marked as nonprecompilable.
If you have explicitly disabled the generation of precompiled files. You can do this with the code
#pragma do_not_precompile |
You can use this as a workaround if you have compiler errors when handling certain header files, but in general, it is not recommended.
If you declare a macro in the top-level compilation unit before a #include that modifies some keyword (or some other identifier never before declared as a macro), the precompiled header mechanism may not rebuild the header file.
For example, if you have declarations in two different files the compiler fails to recognize that the macro defined in file2.c (that redefines int) effectively invalidates all the precompiled headers that are included below it. Consider the two examples below:
#include “hdr.h” |
#define int long #include “hdr.h” |
If any of the following conditions are true, the compiler may abort.
compilation units with templates
the compiler automatically including the source files for the template bodies without them being explicitly #included
the -smart flag is specified
This section describes problems in the Smart Build facility.
Smart Build incorrectly skips recompilation when a macro overrides a non-macro token (for example, a keyword). The precompiled version, using the non-overridden token, will be used. For example, consider the following code:
#define protected public #include “somefile.h” #undef protected |
If the following conditions are true, Smart Build will fail to notice that this new macro redefines some tokens in the include file and will not rebuild the precompiled header file.
The header file somefile.h has been previously seen in a build of some other compilation unit (and has thus been already precompiled into a binary file).
The word protected has not been defined as a macro in any other compilation unit.
You must compile the unit that contains this unusual #define without the -smart option.
Smart Build occasionally skips a recompilation even though a Delta-incompatible change has been made. If the following conditions are true, Smart Build will not detect the change, and will not recompile the changes.
A derived class references a global variable
A new version of a base class then defines a data member with the same name (thus overriding the global — a Delta-incompatible change that should force the derived class members to be recompiled),
You must delete the object file and recompile to force Smart Build to rebuild, which it will do correctly.
In most cases when Smart Build incorrectly skips a file that should have been recompiled, the workaround is to remove the object file(s) and recompile.
When you build executable code from a single source file don't use the -c option, Smart Build may create an empty object file, causing the link to fail. Consider the following command:
NCC -smart x.c -o x |
The first compile will work. NCC will remove the x.o object file as part of its normal operation.
When the second compile is done, SmartBuild will assume that the source file doesn't need to be recompiled and touches the object file, creating an empty object file. The linker prints this message:
Remark(2): Smart Build: recompilation skipped ld: Can't have archive/object only 0 bytes long: x.o |
You must either omit the -smart flag, or compile and link in separate steps, keeping the object file in your directory.