Automatische Optimierung durch den Compiler
Ohne Optimierungsoption (-On, n=0,1,2,3) bzw. mit der Option -no führt der
Compiler nur Grundoptimierungen durch, die den Code an die Hardware der
Maschine anpassen. Im folgenden sollen die verschiedenen Optimierungsstufen
kurz vorgestellt werden. Dabei beinhalten die höheren Stufen immer alle
Operationen der niedrigeren Stufen.
In der Stufe -O0 werden maschinenunabhängige Optimierungen innerhalb eines
Blocks durchgeführt, d.h. innerhalb einer Folge von Befehlen ohne
irgendwelche Sprünge (DO, IF, ...). Beispiele sind das Eliminieren von
mehrfachen Zuweisungen an eine Variable, die zwischendurch nicht benutzt
wird oder das Einführen von temporären Variablen, um Zwischenschritte
abzuspeichern, die mehrmals benutzt werden.
In der Stufe -O1 kommen skalare Optimierungen auf der Ebene von Prozeduren
dazu, z.B. wird eine Variable, der eine Konstante zugewiesen wurde, durch
diese Konstante ersetzt oder skalare Zuweisungen in Schleifen
herausgezogen.
Erst mit Optimierungsstufe -O2 werden Vektorbefehle verwendet. Der Compiler
analysiert verschiedene Formen von Schleifen und wandelt sie, soweit
möglich, in Vektorkommandos um. Er führt zum Teil eine ganze Reihe recht
komplizierter Umformungen durch, um diese ''Vektorisierung'' zu ermöglichen
oder effektiver zu machen. Allerdings gibt es einige
Programm-Konstruktionen, die das automatische Erkennen und Einführen von
Vektor-Befehlen verhindern. Informationen darüber, welche Schleifen, ggf.
mit welchen Methoden, vektorisiert werden konnten, und welche Faktoren dies
bei den anderen Schleifen verhinderten, enthält der Optimierungs-Bericht,
den der Compiler ab -O2 normalerweise ausgibt. Zusätzlich kann er noch
Informationen über die Vektoren selbst enthalten. Mit der Compiler-Option
-or kann man festlegen, ob man nur den Schleifen-Bericht (loop), nur die
Vektor-Informationen (array), beides (all) oder gar nichts (none) haben
möchte. Der Default ist ''-or loop''. Wir werden uns in Abschnitt
4.3
ausführlich damit beschäftigen, wie man solche Hindernisse für die
Vektorisierung u.U. beseitigen kann.
Mit der Option -O3 versucht der Compiler, den Code zu parallelisieren, d.h.
in Teile zu zerlegen, die unabhängig von verschiedenen Prozessoren
bearbeitet werden können. Auf einer Maschine mit vier Prozessoren, auf der
fast immer sechs bis zehn Jobs gleichzeitig laufen, wird man allerdings
selten mehr als eine CPU zur Verfügung haben. Die Parallelisierung von
Programmen ist Thema des Abschnitts 4.4
Die konkreten Auswirkungen der verschiedenen Optimierungsstufen auf das
Programm linalg werden wir durch Vergleich der Laufzeiten von linalg nach
Übersetzen aller Files mit ''FFLAGS=-On'' untersuchen. Die Zeiten messen wir
mit dem /bin/time-Kommando: Der Aufruf ''/bin/time linalg'' bewirkt, daß
zunächst das Programm linalg normal ausgeführt wird, danach werden
zusätzlich drei Zeiten ausgegeben:
- die ''real''-Zeit, die angibt, wieviel Zeit vom Start des Kommandos bis
zum Ende vergangen ist (sie interessiert uns hier nicht, da sie
wesentlich von der Auslastung der Maschine durch andere Benutzer
abhängt),
- die ''user''-Zeit, d.h. die CPU-Zeit, die zur Ausführung des Programms
gebraucht wurde, wobei Zeiten für System-Aufrufe - etwa bei Ein- und
Ausgaben - nicht mitzählen,
- die ''system''-Zeit, die gerade die CPU-Zeit von System-Aufrufen angibt.
Die tatsächliche CPU-Zeit ist also die Summe aus ''user''- und ''system''-Zeit.
Für die verschiedenen Optimierungsstufen ergaben sich folgende Ergebnisse:
C240: |
|
-no |
19 |
. | 0 real |
9.8 user |
0.1 sys |
-O0 |
38 |
. | 2 real |
8.2 user |
0.0 sys |
-O1 |
12 |
. | 8 real |
4.3 user |
0.0 sys |
-O2 |
2 |
. | 3 real |
1.7 user |
0.0 sys |
-O3 |
2 |
. | 5 real |
2.0 user |
0.1 sys |
C3840: |
-no |
4.3 real |
4.1 user |
0.0 sys |
-O0 |
3.3 real |
3.2 user |
0.0 sys |
-O1 |
1.7 real |
1.6 user |
0.0 sys |
-O2 |
0.7 real |
0.6 user |
0.0 sys |
-O3 |
1.1 real |
1.3 user |
0.0 sys |
Man sieht, daß schon die skalaren Optimierungen in -O1 in unserem Beispiel
eine ganze Menge an Geschwindigkeitszuwachs bringen. Der größte Sprung
geschieht aber beim Übergang zur Vektorisierung (-O2). Dies ist nicht
weiter verwunderlich, denn die Convex ist als Vektorrechner von ihrer
Hardware her speziell für Vektorbefehle ausgelegt, die aber erst ab -O2
benutzt werden. Daß beim Parallelisieren die Zeit wieder zunimmt, liegt an
der Definition von ''user''- und ''system''-Zeit, die jeweils die Summen der
Zeiten der einzelnen CPUs sind. Daß die real-Zeit hier niedriger ist als
die user-Zeit, ist nur möglich bei Parallelisierung. Allerdings wurde
diese Messung gemacht, als keine Batchjobs liefen. Normalerweise würde
auch bei guter Parallelisierung die real-Zeit deutlich höher sein. Wie
man auch bei normal ausgelasteter Maschine das Ausmaß an
Parallel-Verarbeitung bestimmen kann, werden wir in Kapitel 4.4.3)
lernen.
Peter Junglas 18.10.1993