Programmoptimierung
Programmoptimierung
Programmoptimierung
Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.
YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.
Einordnung der Optimierung<br />
<strong>Programmoptimierung</strong><br />
Walter F. Tichy<br />
Universität Karlsruhe<br />
• Die Hauptaufgaben der Implementierungs-<br />
phase sind die Umsetzung des Entwurfs in<br />
korrekte, ablauffähige Programme, die<br />
Dokumentation und das erste Testen.<br />
• <strong>Programmoptimierung</strong> ist in den meisten<br />
Fällen zweitrangig oder ganz unnötig.<br />
• Arten der Optimierung:<br />
I. Laufzeitreduktion<br />
II. Speicherplatzreduktion<br />
III. Cache-Optimierungen<br />
Wahlsprüche zur<br />
<strong>Programmoptimierung</strong> (1)<br />
• Premature optimization is the root of all evil.<br />
– Don Knuth<br />
• Make it work before you make it work fast.<br />
– Bruce Whiteside<br />
• Make if fail-safe before you make it faster.<br />
– Kernighan & Plauger<br />
• Make it clear before you make it faster.<br />
– Kernighan & Plauger<br />
Wahlsprüche zur<br />
<strong>Programmoptimierung</strong> (2)<br />
• The First Rule of Program Optimization:<br />
Don’t do it.<br />
• The Second Rule of Program Optimization:<br />
Don’t do it yet.<br />
– Michael Jackson, Jackson System Design
Wahlsprüche zur<br />
<strong>Programmoptimierung</strong> (3)<br />
• We should forget about small inefficiencies,<br />
say about 97% of the time.<br />
– Donald Knuth<br />
• In non-I/O<br />
I/O-bound programs, a few percent of<br />
the source code typically account for over<br />
half of the run time.<br />
– Donald Knuth<br />
• Before optimizing, use a profiler to locate the<br />
“hot spots” of the program.<br />
– Mike Morton<br />
I. Laufzeit-Reduktion<br />
Wahlsprüche zur<br />
<strong>Programmoptimierung</strong> (4)<br />
• To speed up an I/O-bound program, begin<br />
by accounting for all I/O. Eliminate that which<br />
is unnecessary or redundant and make the<br />
remaining as fast as possible.<br />
– David Martin<br />
• The fastest I/O is no I/O.<br />
– Nils-Peter Nelson<br />
• The cheapest, fastest and most reliable<br />
components of a computer system are those<br />
that aren’t there.<br />
– Gordon Bell.<br />
Fallstudie: N-KörperN<br />
Körper-Problem<br />
• Nur wenn notwendig!<br />
• Finde die Zeitfresser mittels Laufzeitprofilierer<br />
und optimiere nur diese!<br />
• Wenn eine kleine Beschleunigung erzielt<br />
werden soll, arbeite auf der<br />
vielversprechendsten Optimierungsebene.<br />
• Wenn eine große Beschleunigung gewünscht<br />
ist, beachte alle Optimierungsebenen.<br />
• Ein Programm für das Vielkörperproblem simuliert<br />
die Bewegungen von N Körpern im dreidim. Raum,<br />
unter Berücksichtigung ihrer<br />
– Massen,<br />
– Ausgangspositionen,<br />
– Geschwindigkeiten und<br />
– gegenseitiger Anziehungskräfte.
N-Körper-Problem (2)<br />
Algorithmus:<br />
• Zerlegung der Zeitachse in kleine Schritte.<br />
• Da die wechselseitigen Anziehungskräfte<br />
berechnet werden müssen, hat jeder<br />
Zeitschritt eine Laufzeit von O(N²)<br />
• Eine erste Implementierung hatte mit<br />
N=10.000 eine Laufzeit von ca. einem Jahr<br />
auf der VAX-11/780.<br />
• Dies wurde verbessert auf Faktor 400 !<br />
N-Körper-Problem:<br />
Optimierungen (2)<br />
4. Systemabhängige Feinoptimierung<br />
32-bit Gleitkomma anstatt 64-bit Gleitkomma (größere Genauigkeit<br />
durch Baumstruktur).<br />
durch Baumstruktur). => ~ Faktor 2<br />
5. Systemabhängige Feinoptimierung<br />
98% der Zeit in einer Routine; Assemblercodierung.<br />
=> ~ Faktor 2,5<br />
6. Hardware<br />
Gleitkommabeschleuniger => ~ Faktor 2<br />
---------------<br />
Insgesamt: Faktor 400<br />
N-Körper-Problem:<br />
Optimierungen<br />
1. Algorithmen und Datenstrukturen<br />
O(N²) => O(N log N) => ~ Faktor 12<br />
Körper werden als Blätter in einem Baum repräsentiert; innere<br />
Knoten sind Gruppen von Körpern. Bei der Berechnung der<br />
Krafteinwirkungen auf einen Körper können diese Gruppen<br />
verwendet werden.<br />
2. Algorithmen-Tuning<br />
=> ~ Faktor 2<br />
Verdoppelung des Zeitschrittes, Spezialbehandlung für Körper, die<br />
sich sehr nahe kommen (kann im Baum leicht entdeckt werden).<br />
3. Reorganistation der Datenstruktur<br />
Umkonfigurierung des Baumes nach jedem Schritt verringert die<br />
Anzahl der Berechnungen.<br />
Anzahl der Berechnungen. => ~<br />
Faktor 2<br />
Optimierungsebenen<br />
1. Problemstellung (Vereinfache!)<br />
2. Systemstruktur<br />
3. Algorithmen und Datenstrukturen<br />
4. Feinoptimierung<br />
5. Systemsoftware<br />
6. Hardware
1. Problemstellung<br />
Prinzip L1: Einfachheit<br />
• The cheapest, , fastest and most reliable (and<br />
most acurate, most secure, easiest to design,<br />
document, , test and maintain) components of<br />
a computer system are those that aren´t<br />
there.<br />
- Gordon Bell<br />
• Vermeidung übermäßiger Komplexität<br />
– „Schleichende Funktionsanhäufung“<br />
– „Vergoldung“<br />
– „Effekt des zweiten Systems“: Beim zweiten Mal<br />
will man es oft „richtig“ machen und erzeugt<br />
dabei ein übermäßig komplexes System.<br />
Beispiel Überschlagsrechnung<br />
2. Systemstruktur<br />
Prinzip L2: Benutze Überschlagsrechnungen, um<br />
die Leistung eines geplanten Systems<br />
abzuschätzen.<br />
Benutze Überschlagsrechnungen, um folgende<br />
Fragen zu beantworten:<br />
• Genügt ein geplantes System den<br />
Effizienzanforderungen?<br />
• Welche Systemstruktur ist die beste?<br />
Beispiel (2)<br />
An ein zu bauendes System werden N<br />
Anfragen gestellt.<br />
Jede Anfrage besteht aus einem Schlüssel zu<br />
dem der zugehörige (eindeutige) Satz in einer<br />
Datei gefunden und ausgegeben werden soll.<br />
Lösung1: Stapelverabeitung<br />
Anfragen<br />
Sortierung<br />
sortierte<br />
Anfragen<br />
Lösung 2: Wahlfreier Zugriff<br />
sequentieller<br />
Zugriff<br />
sortierte<br />
Datei<br />
Antworten<br />
Anfragen<br />
wahlfreier<br />
Zugriff<br />
Antworten<br />
Datei mit<br />
wahlfr. Zugriff
Beispiel (3)<br />
Technische Daten:<br />
• 10 Mio. Sätze in der Datei; 100 Sätze / Block<br />
• Jede Anfrage sucht einen Satz über einen<br />
(eindeutigen) Schlüssel.<br />
• Sequentielles Lesen eines Blockes: 5 ms<br />
• Wahlfreier Zugriff auf einen Block: 50 ms<br />
• Sequentieller Zugriff liest die ganze Datei.<br />
• Wahlfreier Zugriff liest nur Blöcke, die<br />
gewünscht werden, aber vielleicht mehrmals.<br />
Frage: Wann ist welche Methode schneller?<br />
Vorraussetzungen für<br />
Überschlagsrechnungen<br />
1. „Gefühl“ für Zahlen<br />
2. Bereitschaft zum Experiment<br />
3. Genauigkeit beim Überprüfen der Antworten<br />
4. Technologie-Kenngrößen<br />
5. Mathematik, wo nötig<br />
Beispiel (4)<br />
Lösung:<br />
• Sequentieller Zugriff:<br />
100.000 Blöcke, 200 Blöcke/s,<br />
also 500 s pro Durchlauf<br />
• Wahlfreier Zugriff:<br />
R Sätze in R/20 s<br />
• Damit: R/20 < 500 => R < 10.000<br />
• Für weniger als 10.000 Anfragen ist der<br />
wahlfreie Zugriff schneller.<br />
Gefühl für Zahlen<br />
Millisekunden und Mikrosekunden sind so kleine<br />
Größen, dass es auf den Unterschied kaum<br />
anzukommen scheint.<br />
Es handelt sich aber um einen Unterschied von 3<br />
Größenordnungen!<br />
Beispiel:<br />
Millijahr:<br />
8,8 h Mikrojahr: 32s<br />
3 Größenordnungen Geschwindigkeitsunterschied ist der<br />
Unterschied zwischen einem Sprinter (10m/s) und einer<br />
Raumfähre (10.000 m/s).<br />
Angenommen, Sie bekommen € 500 im Monat von Ihren<br />
Eltern. Was wäre bei einer Steigerung um 3 Größen-<br />
ordnungen?
Geschwindigkeiten<br />
Geschwindigkeiten (2)<br />
m / s<br />
10 -11<br />
10 -10<br />
10 -9<br />
10 -8<br />
10 -7<br />
10 -6<br />
10 -5<br />
10 -4<br />
10 -3<br />
10 -2<br />
10<br />
10<br />
10<br />
Equivalent<br />
Beispiel<br />
3 cm /Jahrhundert<br />
Wachstum von Stalaktiten<br />
3 cm / Dekade<br />
Kontinentaldrift<br />
3 cm / Jahr<br />
Wachstum von Fingernägeln<br />
30 cm / Jahr<br />
Wachstum von Haupthaar<br />
30 cm / Monat<br />
Wachstum von Unkraut<br />
10 cm / Tag<br />
Gletscher<br />
3 cm / h<br />
Minutenzeiger einer Armbanduhr<br />
(5mm)<br />
30 cm / h<br />
Verdauungstrakt<br />
5 cm / min<br />
Schnecke<br />
60 cm / min<br />
Ameise<br />
m / s<br />
10 -1<br />
1<br />
10<br />
10 2<br />
10 3<br />
10 4<br />
10 5<br />
10 6<br />
10 7<br />
10 8<br />
Equivalent<br />
6 m / min<br />
3,5 km / h<br />
35 km / h<br />
350 km / h<br />
60 km / min<br />
600 km / min<br />
6000 km / min<br />
600 km / sec<br />
11.000 km / sec<br />
100.000 km / sec<br />
Beispiel<br />
Riesenschildkröte<br />
Gehen<br />
Laufen (Sprint), 100m in 10s<br />
Propellerflugzeug<br />
Düsenjäger (Mach 3)<br />
Raumfähre<br />
Meteoreinschlag (Erde)<br />
Milchstraße bezogen auf Hydra<br />
LA – Satellit – NY<br />
1/3 Lichtgeschwindigkeit<br />
3 Größenordnungen sind ein riesiger Unterschied!<br />
Technologie-Kenngrößen<br />
Kosten für elementare Operationen<br />
(Bsp. C auf PIII-700)<br />
Operation<br />
Ganzzahl-Operanden (long(<br />
long, , 32 Bit)<br />
Gleitkomma-Operanden (double, 64 Bit)<br />
Konvertierungen:<br />
Ganzzahl => Gleitkomma<br />
Gleitkomma => Ganzzahl<br />
Mathematische Funktionen<br />
Kontrollfluss<br />
+ , -<br />
*<br />
/<br />
+ , -<br />
*<br />
/<br />
Sin<br />
Log<br />
Sqrt<br />
for<br />
ns<br />
1,1<br />
5,4<br />
63<br />
32<br />
34<br />
211<br />
4,9<br />
75<br />
197<br />
310<br />
207<br />
6,1<br />
Technologie-Kenngrößen<br />
• Weitere interessante Größen:<br />
– Zeichenreihen:<br />
• Vergleich<br />
• Kopieren<br />
– E/A Zeiten:<br />
• Lesen/Schreiben eines Zeichens/Ganzzahl<br />
• Plattenzugriff, Netzzugriff<br />
• Plattenzugriffe pro Datenbankoperation<br />
– Grundsoftware:<br />
• Sortieren von 10.000 Ganzzahlen<br />
• Sortieren von 10.000 20-Byte Zeichenreihen<br />
• Suchen einer Zeichenreihe in einer Datei<br />
• Speicherbedarf für eine 1-byte 1<br />
Datei<br />
• Quellzeilen/s verarbeitet vom Übersetzer<br />
• Aufgabe: Bestimme diese Kenngrößen selbst, auf<br />
beliebigem Rechner. (Schleife mit 100.000 Operationen,<br />
Zeit messen, Dividieren, Schleifenkosten abziehen.)
Prinzipien<br />
• Everything should be made as simple as<br />
possible, but no simpler<br />
- Einstein<br />
• Sicherheitsfaktoren mit einschließen<br />
• Zwei Abschätzungen sind besser als eine<br />
(zur Überprüfung der Schätzung).<br />
3. Algorithmen und<br />
Datenstrukturen<br />
Prinzip L3: Speicherung von Zwischenergebnissen<br />
statt Neuberechnung<br />
Prinzip L4: Vorverarbeitung von Daten<br />
Prinzip L5: Teile und Herrsche<br />
Prinzip L6: Dynamisches Programmieren<br />
4. Feinoptimierung<br />
(Code Tuning)<br />
• Verbessern der Laufzeit, ohne die<br />
asymptotische Laufzeit des Algorithmus zu<br />
verändern; lediglich Konstanten werden<br />
verringert.<br />
Fallstudie: Drucker<br />
• Programm erzeugt Kommandos, um Graphiken auf<br />
einem optischen Drucker zu erzeugen.<br />
– 10 Minuten für ein kompliziertes Bild ist zu lange.<br />
• Profilierung des Programms mit 10 Bildern zeigt:<br />
– 70 % der Zeit wird für die Anforderung von<br />
Speicherplatz verwendet.<br />
• Untersuchung der Speicherplatzanforderungen:<br />
– Häufigste Größe:<br />
68.000 mal<br />
– Zweit-häufigste Größe:<br />
2.000 mal<br />
• Verbesserung: Caching<br />
– Spezielle Liste für die am meisten gebrauchte<br />
Blockgröße.<br />
– 20 extra Zeilen Quellcode => Reduktion der Laufzeit<br />
auf 45%<br />
Prinzip L7: Ausnutzung eines häufig auftretenden<br />
Falles.
Problem: Zeichenklassifizierung<br />
Zeichenklassifizierung (2)<br />
• Gegeben eine Reihe von 10 6 Zeichen.<br />
• Klassifiziere jedes als<br />
– Großbuchstabe,<br />
– Kleinbuchstabe,<br />
– Ziffer oder<br />
– Sonstiges.<br />
• (Tritt auf in Textformatierern, Kommando-<br />
interpretierern, , Übersetzern, Makro-<br />
expandierern)<br />
if ((c >= ´a´) and (c = ´A´) and (c = ´0´) and (c
Sphärische Distanzen (2)<br />
• Berechnung benötigt trigonometrische<br />
Funktionen mit 10 Sinus- und Kosinus-<br />
Aufrufen pro Distanz.<br />
=> Mehrere Stunden Laufzeit erforderlich.<br />
• Nach Umrechnung in ein kartesisches<br />
Koordinatensystem kann die Euklidische<br />
Distanz verwendet werden (monoton zum<br />
Winkel).<br />
=> 0,5 Minuten Laufzeit<br />
Prinzip L9: Ausnutzung algebraischer Idenitäten.<br />
Wächterelemente (2)<br />
Verwendung eines Wächterelements zur Einsparung eines<br />
Tests:<br />
int suche(int<br />
(int[]<br />
feld, int wert) ) {<br />
int n = feld.length;<br />
int letztes = feld[n-1]; //letztes Elem.<br />
aufbewahren<br />
feld[n-1] = wert;<br />
//und durch Wächter<br />
ersetzen<br />
int i;<br />
for (i = 0; ; i++)<br />
if (feld[i]] == wert) break;<br />
feld[n-1] = letztes;<br />
if ( i == n-1 1 )<br />
return (letztes<br />
== wert ? n-1 1 : -1);<br />
else<br />
rett<br />
rn i<br />
Wächterelemente<br />
Prinzip L10: Erweiterung von Datenstrukturen um<br />
Wächterelemente.<br />
Beispiel:<br />
Sequenzielle Suche in einem unsortierten Feld<br />
int suche(int<br />
(int[]<br />
feld; int wert) ) {<br />
int n = feld.length<br />
.length;<br />
for (int<br />
i=0;<br />
i
Ausrollen von Schleifen (2)<br />
int suche(int<br />
(int[]<br />
feld, int wert) ) {<br />
int n = feld.length;<br />
int letztes = feld[n-1];<br />
feld[n-1] = wert;<br />
int i;<br />
for (i = 0; i+7 < n; i += 8) {<br />
if (feld[i ] == wert) ) { break;}<br />
if (feld[i+1] == wert) ) { i += 1; break;}<br />
if (feld[i+2] == wert) ) { i += 2; break;}<br />
if (feld[i+3] == wert) ) { i += 3; break;}<br />
if (feld[i+4] == wert) ) { i += 4; break;}<br />
if (feld[i+5] == wert) ) { i += 5; break;}<br />
if (feld[i+6] == wert) ) { i += 6; break;}<br />
if (feld[i+7] == wert) ) { i += 7; break;}<br />
}<br />
for ( ; ; i++)<br />
if (feld[i] == wert) break;<br />
feld[n-1] = letztes;<br />
if ( i == (n-1)(<br />
)<br />
return (letztes<br />
== wert ? n-1 1 : -1);<br />
else<br />
return i;<br />
}<br />
Rekursionseliminierung<br />
Prinzip L14: Rekursionseliminierung<br />
• Sogenannte rechts- oder restrekursive<br />
Funktionen können mechanisch in eine<br />
iterative Form transformiert werden.<br />
• Eine Funktion ist rechts- oder restrekursiv,<br />
falls sie entweder ihren Wert direkt<br />
berechnet, oder ihr Wert das unveränderte<br />
Ergebnis eines rekursiven Ausrufs ist.<br />
Weitere Schleifenoptimierungen<br />
Zusätzliche Prinzipien zum Beschleunigen von<br />
Schleifen:<br />
Prinzip L12: Kombinieren von Schleifen über<br />
denselben Bereich. („loop jamming“)<br />
Prinzip L13: Entfernung invarianter Ausdrücke<br />
aus Schleifen.<br />
Rekursionseliminierung (2)<br />
Transformation von Rechtsrekursion in Iteration:<br />
int g(int<br />
x) ) {<br />
if (B(x)) {<br />
S(x);<br />
return g(E(x));<br />
} else {<br />
T(x);<br />
return p(x);<br />
}<br />
}<br />
Falls g nicht in B, S, T, E und p vorkommt:<br />
int g(int<br />
x) ) {<br />
int x1 = x;<br />
while (B(x1)) {<br />
S(x1);<br />
x1 = E(x1);<br />
}<br />
T(x1);<br />
return p(x1);<br />
}
Rekursionseliminierung: : Beispiel 1<br />
Die Methode anfangAb gibt eine Liste um die ersten<br />
n Elemente gekürzt zurück (oder null, , falls die<br />
Liste zu kurz ist). Die ursprüngliche Liste soll nicht<br />
verändert werden.<br />
interface Liste {<br />
public Element holeElement();<br />
public void setzeElement(Element<br />
element);<br />
public Liste holeNaechstes();<br />
public void setzeNaechstes<br />
Naechstes(Liste<br />
liste);<br />
}<br />
...<br />
public Liste anfangAb(Liste<br />
liste, int n) ) {<br />
if (n > 0 && liste != null)<br />
return anfangAb(liste<br />
liste.holeNaechstes(),<br />
n-1);<br />
else<br />
return liste;<br />
}<br />
Rekursionseliminierung: : Beispiel 2<br />
Rekursive Binärsuche<br />
public suche(int[] feld, int links, int rechts, int wert)<br />
{<br />
if (links wert)<br />
return suche(feld, links, mitte-1, wert);<br />
else if (feld[mitte] == wert)<br />
return mitte;<br />
} else<br />
return -1;<br />
Obwohl hier ein rekursiver Aufruf mehrmals<br />
vorkommt, ist diese Funktion doch rechtsrekursiv.<br />
Eine geringfügige Erweiterung der eingangs<br />
Rekursionseliminierung: : Beispiel 1<br />
Unter Verwendung der vorhergehenden Transformation<br />
kann diese rechtsrekursive Methode in eine iterative Form<br />
gebracht werden.<br />
Die Parameterliste wird hierbei als Einheit betrachtet.<br />
public Liste anfangAb(Liste<br />
liste, int n) ) {<br />
Liste liste1 = liste;<br />
int n1 = n;<br />
}<br />
while ((n1<br />
> 0) && (liste1(<br />
!= null)) {<br />
liste1 = liste1.holeNaechstes<br />
holeNaechstes();<br />
n1 = n1 - 1;<br />
}<br />
return liste1;<br />
Rekursionseliminierung: : Beispiel 2<br />
Iterative Version:<br />
public suche(int[] feld, int links, int rechts, int wert) {<br />
int links1 = links;<br />
int rechts1 = rechts;<br />
}<br />
while (links1 wert)<br />
rechts1 = mitte-1;<br />
else if (feld[mitte] == wert)<br />
return mitte;<br />
}<br />
return -1;
Rekursionseliminierung:<br />
Hilfsfunktion<br />
Sobald eine rechtsrekursive Form vorliegt, ist<br />
eine Transformation in die iterative Form nicht<br />
mehr schwierig. Das Problem ist häufig, eine<br />
rechtsrekursive Form zu finden.<br />
Oftmals lässt siche eine rechtsrekursive Form<br />
mit einer Hilfsfunktion erzielen, die in einem<br />
zusätzlichen Parameter ein Zwischenergebnis<br />
mitführt. Dieses Zwischenergebnis wird vom<br />
letzten rekursiven Aufruf in das Endergebnis<br />
umgeformt und als Wert zurückgegeben.<br />
Rekursionseliminierung: : Beispiel 3<br />
public Liste endeAb(Liste<br />
liste, int n) ) {<br />
return endeAbHilf(liste<br />
liste, , null, n);<br />
}<br />
public Liste endeAbHilf(Liste<br />
liste, Liste resultat, int n) ) {<br />
Liste liste2;<br />
if ((n > 0) && (liste(<br />
!= null)) {<br />
liste2 = new Liste();<br />
liste2.setzeElement<br />
setzeElement(liste.holeElement());<br />
liste2.setzeNaechstes<br />
setzeNaechstes(resultat);<br />
return endeAbHilfs(liste<br />
liste.holeNaechstes(),<br />
(),liste2,<br />
n-1);<br />
} else<br />
return reversiere(resultat<br />
resultat);<br />
}<br />
Die Funktion endeAbHilfs baut das Ergebnis zunächst in<br />
umgekehrter Reihenfolge auf. Am Ende der Rekursion wird die<br />
Reihenfolge der Listemittels reversiere umgekehrt.<br />
(reversiere<br />
kann iterativ implementiert werden).<br />
endeAbHilfs ist rechtsrekursiv und kann leicht in eine<br />
iterative Form gebracht werden.<br />
Rekursionseliminierung: : Beispiel 3<br />
Die Funktion endeAb kopiert die ersten n Elemente<br />
einer Liste (soweit vorhanden) und gibt diese als<br />
Liste in der gleichen Reihenfolge zurück.<br />
public Liste endeAb(Liste<br />
liste, int n) ) {<br />
Liste liste2;<br />
if ((n > 0) && (liste(<br />
!= null)) {<br />
liste2 = new Liste();<br />
liste2.setzeElement<br />
setzeElement(liste.holeElement());<br />
liste2.setzeNaechstes<br />
setzeNaechstes(<br />
endeAb(liste<br />
liste.holeNaechstes(),<br />
n-1);<br />
return liste2;<br />
} else<br />
return null;<br />
}<br />
endeAb ist nicht rechtsrekursiv. Die folgende<br />
Funktion aber ist es:<br />
Zusammenfassung<br />
Laufzeit-Reduktion<br />
Generell:<br />
G1: Wenn eine kleine Beschleunigung erzielt werden<br />
soll, arbeite auf der vielversprechendsten<br />
Optimierungsebene.<br />
G2: Wenn eine große Beschleunigung gewünscht ist,<br />
beachte alle Optimierungsebenen<br />
1. Problemstellung:<br />
L1: Vereinfache!<br />
2. Systemstruktur:<br />
L2: Benutze Überschlagsrechnungen, um die richtige<br />
Alternative auszuwählen.
Zusammenfassung (2)<br />
3. Algorithmen und Datenstrukturen<br />
L3: Speicherung von Zwischenergebnissen statt<br />
Neuberechnung<br />
L4: Vorverarbeitung von Daten<br />
L5: Teile und Herrsche<br />
L6: Dynamisches Programmieren<br />
4. Feinoptimierung<br />
L7: Ausnutzung häufig auftretender Fälle<br />
L8: Vorausberechnung logischer Funktionen<br />
L9: Ausnutzung algebraischer Identitäten<br />
L10: Erweiterung von Datenstrukturen mit<br />
Wächterelementen<br />
Zusammenfassung (4)<br />
6. Hardware<br />
L20: Spezialhardware (z.B. Grafikprozessoren,<br />
Chips für Sprachsynthese, Signalverarbeitung,<br />
etc.)<br />
L21: Fließbandverarbeitung (Pipelining(<br />
Pipelining),<br />
L22: Multiprozessoren, Rechnerbündel (Cluster)<br />
Informatikern ist keine dieser Ebenen heilig; sie<br />
müssen verstehen, was gegebenenfalls die ge-<br />
eignetste Ebene für eine Verbesserung ist, und den<br />
Aufwand hierfür abschätzen können.<br />
Zusammenfassung (3)<br />
L11: Ausrollen von Schleifen<br />
L12: Kombinieren von Schleifen über denselben<br />
Bereich („loop<br />
jamming“)<br />
L13: Entfernen invarianter Ausdrücke aus Schleifen<br />
L14: Rekursionseliminierung<br />
5. Benutzte Systemsoftware<br />
L15: Interpretierer => Übersetzer<br />
L16: Übersetzungsoptimierung (Laufzeitsystem)<br />
L17: Betriebssystemänderung, -spezialisierung<br />
L18: Datenbanksystem-Änderung,<br />
-Anpassung<br />
L19: Neuimplementierung (Spezialisierung) von<br />
Bibliotheksroutinen<br />
II. Speicherplatz-Reduktion<br />
• Selbst bei billigem Speicher kann Platzbedarf<br />
kritisch sein:<br />
– Eingebettete Prozessoren, mobile Geräte haben<br />
wenig Speicher<br />
– Caches sind relativ klein; Überlauf der Caches<br />
verlangsamt Programme dramatisch.<br />
– Schlampiger Verbrauch eines virtuellen<br />
Adressraums kann zu Seitenflattern<br />
(„Thrashing<br />
Thrashing“) führen und damit stark<br />
verlangsamen.<br />
– Reduktion von Speicherbedarf kann ein<br />
schnelleres Programm erzeugen. (Lauf-, , Lade-,<br />
Netzübertragungszeit)<br />
• Sowohl Datenraum als auch Programmraum<br />
können reduziert werden.
Reduktion des Datenraumes:<br />
Neuberechnen<br />
Prinzip S1: Neuberechnen statt Speichern<br />
Beispiel: Berechung der Lohnsteuer anstatt<br />
Speicherung der offiziellen Lohnsteuertabellen<br />
Beispiel: Statt mehrerer Versionen einer Textdatei,<br />
speichere erste Version und Mengen von<br />
Änderungsoperationen<br />
Beispiel: Testdaten zum Regressionstesten:<br />
1. Speichern der Testdaten (Testdaten werden durch<br />
Zufallsgenerator erzeugt.)<br />
2. Speichern des Generatorprogramms plus<br />
Anfangswert des Zufallsgenerators, Erzeugung bei<br />
Bedarf.<br />
Komprimierung (2)<br />
Um X[i,j] zu erhalten:<br />
int getValue(int i, int j) {<br />
for (int<br />
k=SpaltenErster<br />
SpaltenErster[j];<br />
k
Große Datenobjekte<br />
Datenkompression<br />
Prinzip S3: Speichere große, identische Datenobjekte<br />
nur einmal<br />
Beispiel: Zeichenreihen, die mehrmals<br />
vorkommen, nur einmal abspeichern; Zeiger<br />
benutzen.<br />
Beispiel: Speicherung von Kalendern:<br />
Es gibt nur 14 kanonische Kalender (7<br />
Wochentage für den 1.Januar, mit oder ohne<br />
29.Februar)<br />
=> Tabelle gibt für jedes Jahr den richtigen<br />
Kalender an. (Evtl. S1: Tabelle durch Formel<br />
ersetzen.)<br />
Speicherplatzvergabe<br />
Prinzip S5: Dynamische Speicherplatzvergabe<br />
Anstelle von statischer Vergabe, Verbrauch nach<br />
Bedarf:<br />
1. Felder der richtigen Größe dynamisch anlegen;<br />
2. Dynamische Speicherplatzvergabe mit expliziter<br />
Freigabe oder automatischer<br />
Speicherbereinigung.<br />
Das ist bei Java u.ä. ganz selbstverständlich,<br />
bei maschinennahen Sprachen und Systemen<br />
jedoch nicht. Gilt ähnlich für die nachfolgenden<br />
Prinzipien<br />
Prinzip S4: Datenkompression<br />
Beispiel: Packen von kleinen Zahlen in Bytes<br />
N = (A lshift 4) or B<br />
=> A = N rshift 4<br />
B = N and 1111 2<br />
(Allgemein Packen/Entpacken<br />
mit *, +, div, mod)<br />
Beispiel: Speicherung von aufeinanderfolgenden<br />
Änderungen an Textdateien (Versionshaltung(<br />
Versionshaltung)<br />
indem man nur die Unterschiede festhält. Bei<br />
Bedarf kann jede Version aus den Unterschieden<br />
wieder erzeugt werden (siehe S1)<br />
Speicherbedarf für eine Version: < 2% des<br />
Klartextes.<br />
Variable Länge<br />
Prinzip S6: Sätze variabler Länge<br />
Benutze nur soviel Platz, wie benötigt. Dies spart<br />
Platz und Zeit: Leerzeichen müssen nicht<br />
gespeichert und überlesen werden.<br />
Beispiel: Bezeichner, Namen, Listen und<br />
Zeichenreihen sollten variabler Länge sein.
Datenkompression<br />
Prinzip S7: Gemeinsame Nutzung von<br />
Speicherplatz<br />
Beispiel: Halde und Keller wachsen gegeneinander:<br />
Keller frei Halde<br />
Beispiel: Überlagerung zweier Dreiecksmatritzen:<br />
A<br />
B<br />
Reduktion des<br />
Programmraumes<br />
Prinzip S8: Ersetze wiederholte Anweisungen<br />
durch Unterprogramme<br />
Prinzip S9: Minisprachen und Spezial-Interpretierer<br />
für kompakte Darstellung<br />
Prinzip S10: Assembler-Codierung<br />
(nur als letzter Ausweg – alles codieren!)<br />
Fallstudie1: Schachspiel<br />
(Reduktion des Datenraumes)<br />
• Schachendspiel: 4-54<br />
5 Figuren ohne Bauern<br />
Phase 1: Lernphase<br />
Für jede mögliche Position (der 4-54<br />
5 Figuren),<br />
berechne die Distanz bis Schachmatt durch<br />
Rückwärtsrechnung von allen Schachmatt-<br />
Positionen.<br />
Wähle Zug so, dass Position eine geradzahlige<br />
Distanz zu Schachmatt hat.<br />
Phase 2: Spielphase<br />
Programm ist „allwissend“ und spielt fehlerlose<br />
Endprogramme.<br />
Für jede Position speichert das Programm einen<br />
Satz mit 12 Bit, einschließlich der Distanz bis<br />
Schachmatt.<br />
Fallstudie1 (2)<br />
64 Felder => 6 Bit pro Figur, 30 Bit für die Position<br />
aller Figuren.<br />
Benutze diesen Schlüssel in eine Datei.<br />
=> Tabelle mit 2 30 Einträgen, oder etwa 1.07<br />
Milliarden 12-Bit Sätze, was die Kapazität der<br />
damaligen Platte überstieg. Rechenzeit bis Turnier<br />
auch nicht ausreichend.<br />
• Reduktion1: Alle Positionen, die Spiegelungen<br />
bzgl. Der 4 Achsen sind, werden nur einmal<br />
gespeichert.<br />
• Annahme: Weißer König nur in Pos.<br />
1-10; 10; beliebige Pos. kann durch<br />
0-33 Spiegelungen dorthin<br />
normalisiert werden.<br />
1<br />
• => Reduktion auf 10* 64 4<br />
2 3<br />
4 5 6<br />
(Platzersparnis Um Faktor 6)<br />
7<br />
8<br />
9 10
Fallstudie1 (3)<br />
Reduktion 2: Der gegnerische König kann nicht<br />
neben dem weißen König stehen. Daher gibt es<br />
statt 10*64 Positionen der weißen Könige nur 454<br />
legale Positionen, in denen der weiße König auf 1-1<br />
10 steht.<br />
⇒ Reduktion auf 454 * 64 3 ≈ 120 * 10 6 12-bit SätzeS<br />
Insgesamt fast um einen Faktor 10 reduziert;<br />
Die Lernphase reduzierte sich dadurch von einem<br />
Jahr auf einige Wochen.<br />
Fallstudie 2<br />
(Reduktion des Programmraumes)<br />
Apple Macintosh (Ur-Mac;; siehe Byte, Feb.1984)<br />
64 KByte ROM musste das gesamte Betriebssystem<br />
aufnehmen:<br />
– Sorgfältige Unterprogrammdefinition,<br />
– Assembler-Programmierung mit<br />
– Sorgfältiger Registerzuweisung und<br />
– Auswahl der Instruktionen (kurze Sprünge, etc.)<br />
⇒ Programm um Faktor 2 kleiner als ein von einer<br />
höheren Programmiersprache übersetztes (und<br />
durch Assemblercode auch effizienter).<br />
Zusammenfassung<br />
Platzreduktion<br />
• Kosten des Platzbedarfs:<br />
+0% Verfügbarer Platz ausgenutzt<br />
>10% Seiten-Flattern,<br />
Cache-Ping<br />
Ping-PongPong<br />
+∞ kein Platz vorhanden, Programm läuft l<br />
überhaupt nicht<br />
Bestimme die kritischen Datenstrukturen (Groß(<br />
Groß-<br />
verbraucher) – Es rentiert sich, diese zu verkleinern.<br />
Abwägung: Platzbedarf Geschwindigkeit,<br />
Funktionalität,<br />
t,<br />
Robustheit,<br />
Wartbarkeit<br />
(Beachte alle Möglichkeiten und wähle die beste;<br />
Für größere Reduktion, benutze alle.)<br />
Zusammenfassung (2)<br />
Datenraum<br />
S1: Berechnen statt Speichern<br />
S2: Komprimieren von spärlichen Datenstrukturen<br />
S3: Speichere große, identische Datenobjekte nur<br />
einmal<br />
S4: Datenkompression<br />
S5: Dynamische Speicherplatzvergabe<br />
S6: Sätze variabler statt fester Länge<br />
Programmraum<br />
S7: Gemeinsame Nutzung von Speicherplatz<br />
S8: Unterprogramme statt Programmwiederholungen<br />
S9: Minisprachen für kompakte Darstellung<br />
S10: Assembler-Codierung
III. Cache-Optimierungen<br />
• Zeit zum Lesen Hauptspeicher Prozessor:<br />
– Ca. 100-500 Takte (ca. 50-1000 Instruktionen)<br />
• Deshalb Caches: Kleine, schnelle<br />
Zwischenspeicher zwischen Hauptspeicher<br />
und Prozessor<br />
• Bei Speicherzugriff wird kleiner Hauptspeicherblock<br />
(Cachezeile) mit dem gewünschten Datenelement<br />
darin in das Cache geladen.<br />
• Wenn ein Element der gleichen Cache-Zeile<br />
angesprochen wird, dann liefert das Cache das<br />
Datum wesentlich schneller als der Hauptspeicher<br />
(zeitlich und räumliche Lokalität).<br />
• Mehrere Hauptspeicherblöcke konkurrieren um selbe<br />
Cachezeile (Verdrängung) Optimierung möglich! m<br />
Details zur Implementierung<br />
• Realisierung in C++-Klassen<br />
• Gespeichert werden int (32 Bit-) ) Werte<br />
• Pseudocode:<br />
Ein kleines Rätsel<br />
• Womit lassen sich sortierte Folgen schneller<br />
implementieren:<br />
Felder oder verkettete Listen?<br />
• Experiment:<br />
– Einfügen einer Zufallszahl in eine sortierte Folge<br />
– Sequentieller Suche (d.h. keine binäre Suche im<br />
Feld).<br />
• Unterschiede<br />
– Liste: Zeigerverfolgung statt Indizierung;<br />
Einsetzen eines Elementes schnell.<br />
– Feld: Verschieben von Teilfeldern aufwendig<br />
Experiment<br />
Durchschnittliche Zeit pro Element (Intel PIII-450)<br />
S = {};<br />
while (S.size() < n)<br />
S.insert(rand());<br />
());<br />
Was ist schneller: Listen oder Felder?
Matrixmultiplikation<br />
Speicherabbildung in C<br />
• Quadratische Matrizen: c = a * b<br />
• C-Quellcode:<br />
for (i=0; i
ikj-Matrixmultiplikation<br />
Experiment<br />
j<br />
= *<br />
k<br />
j<br />
• Messung einer 800x800 Matrixmultiplikation<br />
• Prozessor Alpha 21164a (500MHz)<br />
ijk-Matrixmultiplikation<br />
173,6s<br />
i<br />
i<br />
k<br />
c a b<br />
...<br />
r = a[i][k];<br />
for (j=0;<br />
j
Messung der Kosten<br />
• Mittels folgender Schleife:<br />
for (i=0; i= n)<br />
j -= = n;<br />
}<br />
• Zeitmessung von jeweils 20 Mio. Iterationen<br />
bei verschiedenen n. . (x[j] ist ein 32Bit-Typ.)<br />
• d = 1 bzw. d = 11<br />
Ergebnis – PIII 450<br />
PentiumIII (450 MHz), L1: 16KB, L2: 512 KB<br />
Ergebnis - UltraSparc<br />
UltraSPARC-Iii<br />
(333 MHz), L1: 16 KB, L2: 2MB<br />
Bessere Cache-Nutzung<br />
• Gruppierung (Blockgröße)<br />
Verbessern der<br />
Cache-Block-<br />
Ausnutzung<br />
• Färbung (Kapazität)<br />
Verkleinern der<br />
Arbeitsmenge von<br />
Blöcken
Bessere Cache-Nutzung (2)<br />
Programmiertechniken (1)<br />
• Komprimierung (Assozitivität(<br />
Assozitivität)<br />
Verringern<br />
der Cache-<br />
Konflikte<br />
Programmiertechniken (2)<br />
• Sequentieller Zugriff statt wahlfreier Zugriff<br />
• Vermeide verzeigerte Datenstrukturen<br />
Beispiel: Vollständiger Binärbaum als Feld mit<br />
impliziten Zeigern (spart Zeiger).<br />
0<br />
1 2<br />
0 1 2 3 4 5 6<br />
• Komprimiere Daten, damit möglichst viele<br />
Elemente gleichzeitig in den Cache passen.<br />
– Benutze Datenkompression (siehe Speicheropt.)<br />
– Aufspalten von Objekten in häufig und selten<br />
benutzte Anteile.<br />
– Parallele Felder statt großer Verbunde.<br />
1 1<br />
2 2<br />
3 3<br />
4 4<br />
5 5<br />
6 6<br />
7 7<br />
Programmiertechniken (3)<br />
• Cache-bewusste<br />
Allokation<br />
– Verkettete Elemente hintereinander anlegen<br />
(u.U. mit speziellem Allokator)<br />
– Cache-bewusste<br />
Baumorganisation:<br />
1<br />
1<br />
2<br />
3<br />
4<br />
5<br />
6<br />
7<br />
2 3 4 5<br />
2<br />
4<br />
5<br />
1<br />
3<br />
6<br />
7<br />
Sollte nicht<br />
aus Cache<br />
verdrängt<br />
werden!<br />
3 4 5 6<br />
• Blockbildung bei Matrizenoperationen<br />
– jeweils Teilblöcke, die in Cache passen.<br />
Hauptspeicher<br />
1<br />
2<br />
3<br />
leer<br />
4<br />
5<br />
Cache