26
Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Embed Size (px)

Citation preview

Page 1: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Seminar aus Softwareentwicklung: Programmierstil

Effizienz

Friedrich Priewasser

Page 2: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Übersicht• Überschlagsrechnungen• Profiling• Code Tuning• Effiziente Speichernutzung

Page 3: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Überschlagsrechnungen• Ermöglichen das Abschätzen von Laufzeiten

und Speicherbedarf• Schon vor Implementierung kann

Machbarkeit überprüft werden• Für jede Operation wird die

durchschnittliche Ausführungsdauer gemessen:n=...for (int i=0; i<n; i++);

n=...for (int i=0; i<n; i++) i1=i2+i3;

Page 4: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Kosten für eine Operation• Probleme:

Compileroptimierungen verwendeter Speicher (Cache oder

Hauptspeicher) Ergebnis nur für ähnliche Prozessoren einsetzbar

• an „sinnvollen“ Bsp. überprüfen ob Schätzung stimmt (Größenordnung)

• Sicherheitsfaktoren verwenden

Page 5: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Profiling• Profiler: Werkzeug zum Auflisten der Häufigkeit

mit der ein Programmteil ausgeführt wurde• Bsp: Primzahlen bis 1000 ermitteln:

int prime(int n){ int i; for (i=2; i<n; i++) 999 if (n % i == 0) 78022 return 0; 831 return 1; 168}

main(){ int i,n; n=1000; 1 for (i=2; i<=n; i++) 1 if (prime(i)) 999 printf("%d\n", i); 168}

999 Zahlen werden überprüft 168 Zahlen sind prim

Page 6: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

int root(int n){ return (int) sqrt((float) n); 5456}

int prime(int n) { int i; for(i=2;i<=root(n);i++) 999 if (n%i == 0) 5288 return 0; 831 return 1; 168}

main(){ int i,n; n=1000; 1 for (i=2; i<=n; i++) 1 if (prime(i)) 999 printf("%d\n", i); 168}

5288 statt 78022 Tests

Laufzeit steigt allerdings

Reduzieren der Testsauf Teilbarkeit

Page 7: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

%Zeit Name 82.7 sqrt 4.5 prime 4.3 root 2.6 frexp ... ...

int prime(int n){ int i, bound; bound = root(n); for (i=2; i<=bound; i++) if (n%i == 0) return 0; return 1;}

int prime(int n){ int i; for (i=2; i*i<=n; i++) if (n%i == 0) return 0; return 1;}

Wurzelberechnung benötigt über 4/5 der Gesamtzeit• Arbeit innerhalb Schleifen minimieren

• Komplexe Funktion durch einfache ersetzen

Verwenden eines Profilersmit Zeitmessung

Page 8: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Code Tuning• Gründe die gegen Code Tuning sprechen:

optimierter Code ist schwierig zu programmieren, zu lesen und zu

überarbeiten fehleranfällig mit viel Zeitaufwand beim Erstellen verbundenim schlimmsten Fall Langsamer

• Falsche Vermutungen können die Laufzeit erhöhen das Aus für das Projekt

• Zu frühes Optimieren führt zu nicht korrekten, schlecht modularisierten Code

• Programmcode (noch) nicht optimieren

Page 9: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Methoden zur Geschwindigkeitssteigerung

• Programm Design überdenken (Modularisierung, Grobentwurf, ...)

• Modul- und Methodendesign überarbeiten (Wahl geeigneter Datenstrukturen und Algorithmen)

• Zugriffe auf Betriebssystem reduzieren (Ausgabe auf Bildschirm, Festplatte, ... Einlesen von Festplatte, ...)

• Geeigneten Compiler wählen (Compileroptimierungen)

• Andere Hardware verwenden (Hardware ist billiger als Software)

• Code Tuning

Page 10: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Vorgehensweise beim Code-Tuning

• Geschwindigkeit des Programms messen ca. 5% des Codes benötigen über 50% der

Laufzeit• „Hot Spot“ im Programm überarbeiten,

„tunen“• Erfolg der Optimierung überprüfen

Ist Programm wirklich schneller geworden? Läuft es weiterhin fehlerfrei?

• Sinnhaftigkeit weiterer Optimierung überdenken

Page 11: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Ein Beispiel:Zweier-Logarithmus-Berechnung für Integer

static uint Log2(uint n){ return (uint) (System.Math.Log(n)/System.Math.Log(2));}

static uint Log2(uint n){ return (uint) (System.Math.Log(n)/0.6931471805599453094);} 450ns

700ns

static uint Log2(uint x){ if(x<0x2) return 0; if(x<0x4) return 1; if(x<0x8) return 2; if(x<0x10) return 3; ... if(x<0x20000000) return 28; if(x<0x40000000) return 29; if(x<0x80000000) return 30; return 31;} 120ns

• Ersetzen von Funktionsaufrufen durch Ergebnis

• Geeignete Datentypen verwenden / Algorithmus ändern

Page 12: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

static uint Log2(uint x){ if(x<0x10000){ if(x<0x100){ if(x<0x10){ if(x<0x4){ if(x<0x2) return 0; else return 1; } else { if(x<0x8) return 2; ... else return 29; } else { if(x<0x80000000) return 30; else return 31;} } } } } 40ns

Vergleich: Originalversion 700nsmit Konstante 450ns -36%ohne Math.Log 120ns -83%mit Binärsuche 40ns -94%

Ein Beispiel:Zweier-Logarithmus-Berechnung für Integer

• Algorithmus verbessern

Page 13: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Komplizierte Operationen durch einfache ersetzen

• Positionsbestimmung bei Zyklischer Puffer:pos=(pos+1) % n;

ersetzen durch:pos++;if(pos>=n) pos=0;

val=0;for(int p=0;p<=power;p++) val=val+coef[p]*Math.pow(x,p);

ersetzen durch:

• Polynom-Auswertung

val=0;powerOfX=1;for(int p=0;p<=power;p++){ val=val+coef[p]*powerOfX; powerOfX*=powerOfX;}

val=0;for(int p=power;p>=0;p--) val=val*x+coef[p];

Weitere Verbesserungdurch Ändern desAlgorithmus:

Page 14: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Inline-Codierung• Vermeidet Aufwand des Funktionsaufrufs• Beliebtes Mittel in C: Makros

Bsp.: max Funktionint max(int a, int b){ return a>b ? a : b;}

#define max(a,b) ((a)>(b) ? (a) : (b))

Je nach Compiler bis zu 50% schneller

wird ersetzt durch

Page 15: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

int[] x={5,2,1,3};int max=arrmax(4);

3<5 => arrmax(3) berechnen 1 > arrmax(2) ? 1 : arrmax(2) 1<5 => arrmax(2) berechnen

2 > arrmax(1) ? 2 : arrmax(1) 2<5 => arrmax(1) berechnen

5

5

2 > arrmax(1) ? 2 : arrmax(1) 2<5 => arrmax(1) berechnen

5

5

3 > arrmax(3) ? 3 : arrmax(3) 3<5 => arrmax(3) berechnen

1 > arrmax(2) ? 1 : arrmax(2) 1<5 => arrmax(2) berechnen

2 > arrmax(1) ? 2 : arrmax(1) 2<5 => arrmax(1) berechnen

5

5

2 > arrmax(1) ? 2 : arrmax(1) 2<5 => arrmax(1) berechnen

5

5

Beispiel zur Anwendung:

Komplexität steigt durch Verwenden des Makros von O(n) auf O(2n)

int arrmax(int n){ if (n==1) return x[0]; else return max(x[n-1],arrmax(n-1));}

Page 16: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Loop-Unrollingfor (int i=0;i<5;i++) a[i]=i;

a[0]=0; a[1]=1; a[2]=2; a[3]=3; a[4]=4; 6.5 mal schneller = 85% Zeit ErsparnisAllgemein:

i=1;while (i<=Num){ a[i]=i; i++;}

i=1;upper=Num-N+1;while(i<=upper){ a[i]=i; a[i+1]=i+1; ... a[i+N-1]=i+N-1; i+=N;}while(i<=N){ a[i]=i; i++;}

Page 17: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Speichern für Wiederverwendung• Einmalige Berechnung von

Funktionsergebnissen zur Implementierungszeit (Ergebnisse in Datei

speichern) zur Initialisierungszeit bei erstem Aufruf

• z.B.: Tabelle mit vorberechneten Sinuswerten

0 bis 90 Grad (Rest kann berechnet werden) 0.1 Grad Schrittevoid InitTab(){

for(int x=0;x<=900;x++) sinTab[x]=Math.sin(x);}

double SinTab(int n){ if(sinTab[n]<-1) sinTab[n]=Math.sin(n/10); return sinTab[n];}

bei Initialisierung: bei erstem Aufruf:

Page 18: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Schreiben von Programmteilen in Assembler

• Vorgehensweise Programm vollständig in Hochsprache schreiben Testen und feststellen ob das Programm den

Anforderungen entspricht Feststellen welche Teile des Codes nicht schnell

genug arbeiten (Profiler) Vom Compiler erzeugten Assembler-Code

optimieren Korrektheit und Geschwindigkeitsgewinn

überprüfen bzw. messen• Nachteil: Portabilität geht verloren

Page 19: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Compiler Optimierungen• Kosten nichts• Leistungsgewinn hängt ab von

Programmcode Sprache CompilerBereich: 0 bis 50 Prozent

Page 20: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Weitere Techniken• Gleich oft durchlaufene Schleifen

zusammenfassen• Arbeit innerhalb Schleifen minimieren• Tests beenden wenn Ergebnis bekannt ist

Mit break Schleife beenden• „Sentinels“ verwenden beim Suchen in Arrays

Letztes Element durch gesuchten Wert ersetzen Ersetzt dir Abfrage ob der Index noch gültig ist

• if else und switch Statements der Häufigkeit nach ordnen

Page 21: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Speichereffizienz• Bsp.: Geographische Datenbank:

200x200 Felder 2000 Nachbarn geg.: x und y Position ges.: Nr. des Nachbarn

0

0

5

7

17

98

538

1171

965

162

Einfachste Lösung: Array mit 200x200 Einträgen 40000 Elemente: bei 32-Bit Werten 160 000 Byte bei 16-Bit Werten 80 000 Byte

Page 22: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

0

1

2

2 17

1 98

5 538 126 1053

138 15

row nextpointnum

colhead

Suchaufwand: Max: über 200 Punkte Mittel: über 10 PunkteSpeicherbedarf: 200*4 Byte + 2000*12 Byte = 24800 Bytedurch malloc steigt der Bedarf auf das mehrfache

Verwenden verketteter Listen:

Page 23: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

17 538 1053 98 15 1800 ... 437 8322 5 126 1 138 11 ... 11 67

0 3 5 5 ... 1889 2000

pointnum row

firstincol 0 1 2 3 199 200

Suchaufwand: Max: über 200 Punkte Mittel: über 10 PunkteSpeicherbedarf: bei 32-Bit Werten: 201*4 Byte + 2000*4 Byte + 2000*4 Byte = 16804 Byte bei 16-Bit Werten: 201*2 Byte + 2000*2 Byte + 2000*2 Byte = 8402 Byte

find(int i,int j){ for (k=firstincol[i];k<firsincol[i];k++){ if (row[k]==j) return pointnum[k]; } return -1;}

Ersetzen der Listen durch eine Liste mit fixer Länge:

Page 24: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

find(int i,int j){ for (k=firstincol[i];k<firsincol[i];k++){ if (point[poinnum[k]].row==j) return pointnum[k]; } return -1;}

Speicherbedarf bei 16 Bit-Werten: 201*2 Byte + 2000*2 Byte = 4402 ByteVergleich: 32 Bit 16 Bit2-dim Array: 80 000 Byte 40 000 ByteLinked-Lists: >= 42 800 Byte1-dim Arrays: 16 804 Byte 8 804 Byte ohne „Row“ 8 804 Byte 4 402 Byte

Platzersparnis: 75.6 kB = 94.5%

Entfernen des Row-Arrays:

Page 25: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

ZusammenfassungSpeichereffizienz

• Kleinst mögliche Wert-Typen verwenden• Werte neu berechnen statt speichern• Geeignete Datenstrukturen verwenden

Keinen Platz für Null-Werte verschwenden Menge an Hilfsdaten (Zeiger, ...) reduzieren

• Nicht übertreiben (Jahr 2000 Problem)

Page 26: Seminar aus Softwareentwicklung: Programmierstil Effizienz Friedrich Priewasser

Schlüsselpunkte beim Optimieren

• Performance alleine führt nicht zu guter Softwarequalität

• Wenige Prozent des Codes (ca. 5%) benötigen über 50% der Laufzeit

• Messen der Geschwindigkeit (vor und nach der "Optimierung") ist das A und O des Code Tuning

• Nur von Anfang an sauberer Code führt zu guten Ergebnissen