01.03.2014 Aufrufe

Programmoptimierung

Programmoptimierung

Programmoptimierung

MEHR ANZEIGEN
WENIGER ANZEIGEN

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

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!