Mythos oder schlichte Selbstverklärung, dass der Mensch der Maschine überlegen sei: Die Auffassung, dass handoptimierter Maschinencode effizienter als der von einem C-Compiler sei, hält sich in Entwicklerkreisen hartnäckig. Dabei hat sich viel getan im Compilerbau seit den Anfängen mit Kernighan und Ritchie. Die Forschungsergebnisse dieser Disziplin sind umfangreich und kompliziert, sodass sich mancher Entwickler fragt, was davon Einzug in reale Übersetzer gefunden hat.
Diet-Libc-Entwickler Felix von Leitner hat dazu eine Reihe von Programmen für einen Vortrag auf dem Linux-Kongress unter die Lupe genommen [1]. Die einzelnen Werkzeuge unterscheiden sich in großem Maße in Hinsicht auf Ausstattung, Entwicklungsoberfläche, Einsatzzweck und Optimierungsphilosophie, aber alle übersetzen C-Code für die X86-Plattform und sind kostenlos einsetzbar (siehe Tabelle 1). Einige erfordern dazu eine Registrierung und machen den kommerziellen Einsatz von einer Reihe von Bedingungen abhängig.
|
|
|
Name
|
Kürzel und Version
|
Lizenz
|
Kosten
|
|
GNU Compiler Collection [2]
|
GCC 4.4.2
|
GPL
|
kostenlos
|
|
Intel Compiler Suite Professional Edition [3]
|
ICC 11.1
|
proprietär
|
kostenlos
|
|
Sun Studio C Compiler [4]
|
SSC 12.1
|
proprietär
|
kostenlos für Mitglieder des Sun Development Network
|
|
Low Level Virtual Machine Compiler Infrastructure [5]
|
LLVM 2.6
|
GPL
|
kostenlos
|
|
Microsoft Visual Studio 2008 [6]
|
MSVC 9.0
|
proprietär
|
kostenlos in der Express-Version
|
Fünf im Vergleich
Der Klassiker für den Linux-Entwickler ist sicherlich der GNU-C-Compiler, den er mit »gcc« aufruft und der verwirrenderweise Teil der gleichnamigen GNU C Compiler Collection ist [2]. Richard M. Stallman veröffentlichte die erste Version bereits 1987. Ende der 1990er Jahre eröffnete das EGCS-Projekt wegen interner Uneinigkeit einen Fork, vereinigte sich jedoch noch kurz vor der Jahrtausendwende wieder mit der Hauptlinie.
Intels Compiler startete in der aktuellen Form erst nach dem Jahrtausendwechsel und gilt als stark optimiert für diverse CPU-Technologien aus seinem eigenen Haus [3]. Er vermag Vektoreinheiten wie SSE und seine Nachfolger effizient anzusprechen. Suns Studio übersetzt wie die alle anderen Produkte ebenfalls C++ [4]. Der Hersteller bietet Zusatzwerkzeuge fürs Profiling und für Performance-Tests an, die besonders unter Open Solaris ihre Stärken ausspielen [5].
Die Universität Illinois in Urbana-Champaign hat die Low Level Virtual Machine (LLVM) entworfen, einen Zwitter aus klassischem Compiler und einer VM wie bei Java: Den erzeugten Bytecode kann das System auch zur Laufzeit noch optimieren [6]. Zwar nur bedingt unter Linux einsetzbar, übersetzt auch Microsofts Visual Studio C-Code [7].
Konstanten im Speicher
Eine erste Optimierungsmöglichkeit bieten Konstanten, denn sie lassen sich im C-Quelltext auf mehrere Weisen notieren (siehe Listing 1). Im Quelltext selbst sind sie unter Programmierern verpönt, da dann zu viele Stellen auf einmal zu ändern wären. So arbeiten aber aus Sicht des Compilers Makro-Konstanten wie in Zeile 1, denn der Präprozessor expandiert sie bereits vor dem Übersetzungsschritt. Wer den Qualifier »const« verwendet (Zeile 3) darf mit dem Bezeichner lesend so umgehen wie mit einer Variable, hat aber Vorteile beim Debugging, denn diesmal weiß der Compiler überhaupt erst von dem Namen.
01 #define KONSTANTE 23
02
03 const int konstante = 23;
04
05 enum { _konstante = 23 };
06
07 void foo(void) {
08 bar(KONSTANTE + 3);
09 bar(konstante + 4);
10 bar(_konstante + 5);
11 }
|
Für Integer lässt sich der etwas sperrig zu notierende Aufzählungstyp in Zeile 5 verwenden, der verbraucht dann im Code aber auch keinen Extraplatz. Insgesamt finden alle Compiler heraus, dass der Rechenausdruck in den Zeilen 8 bis 10 immer konstant bleibt, und ersetzen ihn jeweils direkt im Code durch die Ergebnisse 26, 27 beziehungsweise 28 (siehe Listing 2).
01 foo:
02 subq $8, %rsp
03 movl $26, %edi
04 call a
05 movl $27, %edi
06 call a
07 movl $28, %edi
08 addq $8, %rsp
09 jmp a
|
Eine ähnliche Frage wie bei Konstanten stellt sich Entwicklern bei kurzen Codestückchen, die sie häufig aufrufen. Funktionsaufrufe bedeuten nach der reinen Lehre, ihre Argumente auf den Stack zu legen, zum Code zu springen, die Argumente abzuholen, die Funktion auszuführen und auf gleiche Weise zurückzukehren. Daher versuchen manche, die Funktion direkt einzubetten, indem sie diese per »#define« festlegen. Eine bessere Wahl ist, die Funktion als »inline« zu markieren, dann erledigt das der Compiler und erlaubt trotzdem, per Debugger Breakpoints zu setzen.