Open Source im professionellen Einsatz

© itsallgood, Fotolia.com

Wie Compiler und Entwickler effizienten Assembler-Code erzeugen

Bit-Tuning

C-Programmierer vertrauen ihrem Compiler, wenige nur kommen mit Assembler in Berührung. Viele schließen sich der vorherrschenden Meinung an, dass einige Verrenkungen für performanten Code nötig seien. Eine Untersuchung zeigt: Die meisten Übersetzer sind schlauer als erwartet - wenn man sie lässt.

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.

Tabelle 1:
C-Compiler

 

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.

Listing 1: Definition von
Konstanten

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).

Listing 2: Übersetzte
Konstanten

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.

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