Open Source im professionellen Einsatz

Compiler-Läufe mit Cache optimieren

Abkürzungen durch den Header-Dschungel

Programmierer kennen das Problem mit der Wartezeit beim Kompilieren nur zu gut: Sie ist zu kurz für eine andere Tätigkeit und dauert zu lange, um sie zu ignorieren. Dieser Artikel stellt ein Verfahren vor, das die Kompilierzeiten drastisch reduziert.

Angenommen Sie entwickeln ein Projekt in C oder C++ mit 50 Sourcefiles ( .cpp) und 50 Includefiles ( .hpp). Außerdem haben Sie ein Makefile entweder von Hand oder über Automake und Autoconf oder auf andere Weise erstellt. Sie starten Make und das Projekt kompiliert komplett durch. Wenn Sie in einem Sourcefile irgendeine Subroutine ändern, kompiliert Make erwartungsgemäß lediglich das geänderte Sourcefile und linkt das Programm danach neu.

Nun korrigieren Sie jedoch in einem Headerfile einen Tippfehler in einem Kommentar. Unglücklicherweise benötigen 45 der 50 Sourcefiles dieses Headerfile. Daher wird Make alle 45 Files neu kompilieren. Sie schauen zu und wissen genau, dass das Ergebnis identisch mit der vorigen Kompilierung sein wird - aber Make kann das nicht wissen.

Für Make ist ein Projekt ein Graph aus Dateien und ihren Abhängigkeiten. Make sieht lediglich, dass alle 45 Files von dem Includefile abhängen und dass es geändert wurde. Ergo müssen alle neu kompiliert werden. Der Compilercache erkennt diese unnötigen Kompilierungen und holt die Ergebnisse aus dem Cache, was den gesamten Vorgang wesentlich beschleunigt.

Weggabelungen

Der Compilercache ist ein Bash-Skript, das statt des bisherigen Compilers aufgerufen wird. Es entscheidet, ob eine Kompilierung wirklich nötig oder ob das Ergebnis schon im Cache ist. Falls eine Kompilierung notwendig ist, wird der Compiler aufgerufen, falls nicht, wird das Ergebnis aus dem Cache kopiert.

Die Entscheidung, ob ein File neu kompiliert werden muss, hängt jedoch nicht wie bei Make davon ab, ob der Quelltext neuer ist als das zugehörige Objectfile, der Compilercache führt stattdessen eine Analyse des zu übersetzenden Quelltextes durch. Das funktioniert zwar langsamer als Make, erfordert dafür aber wesentlich weniger Neukompilierungen. Im Ergebnis ist mit Compilercache ein deutlicher Geschwindigkeitsvorteil zu erzielen.

Die Vorteile von Compilercache schildert die README-Datei des Pakets im Einzelnen [1]. Im Kern wird ein Quelltext nicht neu übersetzt, wenn lediglich Reformatierungen wie das Einfügen von Leerzeichen zur besseren Lesbarkeit vorgenommen oder Kommentare abgeändert oder hinzugefügt wurden.

Installation

Holen Sie sich zunächst den Compilercache von [1]. Die Installation erfolgt als Benutzer und beeinträchtigt die anderen User nicht (eine systemweite Installation wird im README beschrieben). Tippen Sie dann:

DUMMY="compilercache-1.0.9"
CURDIR="$(pwd)"
tar xzvf ${DUMMY}.tar.gz
cd ${DUMMY}/src
./compile.sh
cd $HOME
echo COMPILERCACHEBINDIR=${CURDIR}/  ${DUMMY}/bin > .compilercacherc

Vergessen Sie nicht die Version 1.0.9 durch die jeweils aktuelle zu ersetzen. Um sicher zu sein, können Sie die Installationsanleitung auch aus dem derzeit aktuellen README lesen. Nun müssen Sie, um den Compilercache zu nutzen, noch Ihre PATH-Umgebungsvariable anpassen, von Hand oder per Login-Skript:

export PATH=${HOME}/compilercache-  1.0.9/bin:${PATH}

Ab jetzt verwenden Sie automatisch den Compilercache. Die vielen Konfigurationsmöglichkeiten und weitere Feinheiten lesen Sie im README, dessen aktuelle Version auch auf der Homepage steht. Wenn Sie nun den Compiler aufrufen, erhalten Sie automatisch Debug-Meldungen, die über Cache-Hits und Cache-Misses informieren. Man kann die Meldungen auch abschalten.

Problemfall

Wenn nirgendwo benutzte Makrodefinitionen verändert wurden, entfällt das Kompilieren. Das mag zunächst unsinnig erscheinen, denn wer ändert schon unbenutzte Makros? Im Linux-Kernel gibt es aber ein Paradebeispiel für dieses Problem: Die Datei include /linux/autoconf.h im Kernel-Source enthält alle Konfigurationsdaten des Kernels. Fast jedes C-File im Kernel bindet dieses Includefile ein. Macht man nun eine winzige Änderung an der Konfiguration eines Treibers, muss der komplette Kernel neu übersetzt werden. Mit Compilercache wird hier nur der Treiber selbst neu kompiliert.

Wird ein Projekt gelöscht und neu entpackt, so läuft die Kompilierung rasant schnell ab, da die Ergebnisse alle noch im Cache sind. Das nützt bei neuen CVS-Checkouts oder dem Aufspielen neuer Versionen solcher Pakete, bei denen sich lediglich ein paar Sourcen verändert haben.

Ist der Cache via NFS gemountet, kompiliert in einer Arbeitsgruppe von zehn Leuten fortan jeweils nur ein einziger die Quelltexte und spart damit die Zeit der anderen. Das wirkt sich gerade beim verteilten Entwickeln mit CVS sehr positiv aus - wenn das NFS schneller ist als die Neukompilierung.

Diesen Artikel als PDF kaufen

Als digitales Abo

Als PDF im Abo bestellen

comments powered by Disqus

Ausgabe 07/2013

Preis € 6,40

Insecurity Bulletin

Insecurity Bulletin

Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...

Linux-Magazin auf Facebook