On another front, while computer systems slowly increase in power, the problems they are meant to solve grow as well, usually faster than the computers. The software designed to solve the problems grows, and as the software size increases, it too becomes a problem needing a solution. With today's networks of workstations and servers, it is fairly common to want to run a given application across the various machines in the network. This is easily done for small, simple applications but becomes more complicated as the types of machines and the program sizes increase. Maintaining multiple copies of source code for multiple target architectures consumes disk space, and makes updates more tedious. While sharing filesystems across the network with protocols like NFS eases this problem, it does not go far enough.
The purpose of this paper is to describe an environment that utilizes a multi-processor computer system as a base for a centralized program compilation server. This environment provides high speed compilation of large program sets for multiple target architectures. Other benefits include a reduction in the amount of disk space required for a package, and a simple reconfiguration process.
Section 2 examines the problems with compiling large program sets such as the X Windows System. The principal problem needing to be solved is jut the lengthy compile time, which is due partly to the configuration process. Section 3 presents methods to restructure the software configuration allowing easier reconfiguration and parallel compilation, using the features of GNU Make. This approach speeds up compilation significantly, but only for the multi-processor machine. Section 4 extends the benefits to other architectures through use of the GNU C compiler, and a summary is provided in Section 5.
2 The Typical Makefile
The intent of this work is to take advantage of a multi-processor machine to speed up program
compilation, and then to allow other machines to benefit from the speed up. One would like to use
parallel make to speed up the compilation step, and in a networked environment, it is desirable to
share a single copy of a program's source code across all the machines using the program, thus
minimizing the space required as well as making it easier to manage software updates. The typical
approach to configuring and compiling software presents a number of problems preventing this from
occurring.
The X software distribution makes an ideal case study because the problems it presents are so extreme. Managing the source tree can be a tremendous task in itself, as just the core X distribution consumes on the order of 100 megabytes of storage. Actually compiling the code can consume several hours of time, and modifying the configuration usually demands a complete restart of the build procedure.
It would be nice if simply using a make that supports parallel operations was all that was necessary to speed things up, but typical makefiles prevent effective parallel use in the following two ways:
mk-dirs: for i in $(SUBDIRS); \ do \ mkdir $$i \ doneThis behavior is not so bad, it just negates the advantage of using a parallel make.
subdirs: libs extensions clients demos cd $?; makeHere a parallel make would build the programs in the clients and demos directories before the libraries they require are complete. This behavior is almost certain to cause the make to fail.
Writing a proper parallel makefile is not difficult, and with GNU make it is easier still. Most
for-loops in makefiles are intended to perform an action for a collection of subdirectories. The
previous for-loop example could be rewritten quite simply:
On a large multi-processor system with high I/O bandwidth, parallel make increases the speed
of compilation linearly with the number of available processors. This is a big win, particularly
for packages like X where the release notes warn that the process can take up to 12 hours. On
an Alliant FX/8 (no giant by today's standards) the original X build process takes five hours,
but eliminating imake and running in parallel cuts that time to less than an hour, using only
four CPUs. With faster systems, like an Alliant FX/2800 with a dozen processors, the build time
can be reduced to only a few minutes. While intense number-crunching jobs may be hard to port
to parallel architectures, this is clearly a case where multi-processors shine.
Another handy feature is the "include" command, which allows a makefile to reference another file.
With the include command all of those copies of the 10K Imake template in the X sources can be
deleted, immediately recovering over a megabyte of disk space. Also, using the include file means
that makefiles only need to be written once, and any configuration changes need only be made in
a single make include file. As an additional refinement, two include files may be used, one
solely for macro definitions and the other for commonly used rules. Taking the optimizations
all the way, the macro include can be surrounded with ifdef's, so the macros will only need to
be read once per build, in the highest level makefile. 3 Building with GNU Make
The X developers recognized that make and makefiles are inherently nonportable. The Imake solution
is inelegant at best, and is not recommended. The approach taken here is to standardize the
makefiles by requiring a standard version of make - GNU make. There are many advantages of this
approach over the Imake solution, not the least being that some systems' standard make program
still have problems with Imake makefiles.
mk-dirs: $(SUBDIRS)
mkdir $?
Any garden-variety make program could handle this example. For most situations though, where
the subdirectories already exist and the goal is to perform an action in that directory, some
trickery is necessary. The following illustrates an example based loosely on the Xlib sources:
SUBLIB = lib
SUBPRG = clients demos
SUBDIRS = $(SUBLIB) $(SUBPRG)
all:
@(MAKE) $(SUBPRG) TARG=all
clean includes install:
@$(MAKE) $(SUBDIRS) TARG=$@
$(SUBDIRS)::
@cd $@; echo making $(TARG) in $@...; \
$(MAKE) $(TARG)
$(SUBPRG):: $(SUBLIB)
.PHONY: $(SUBDIRS)
This example demonstrates a number of points. First, it shows how to write a set of rules to
operate in a collection of subdirectories without using a for-loop, giving parallel make a
chance to work. The example also shows how to structure the targets so that the dependency
hierarchy is preserved. (E.g., the parallel build of "clients" and "demos" in this example will
not occur until the "lib" directory is complete.) The crucial feature of GNU Make that lets it
all work is the ".PHONY" target. In GNU Make, any dependencies of the .PHONY target are always
treated as out-of-date, so their associated commands will always get executed. This feature is
what allows the subdirectory rules to execute.