Was tatsächlich gemacht wird, kann sich von Compiler zu Compiler Version ändern. Es gibt wenig "generelle", grosse Optimierungen, sondern viele kleine Dinge die getan werden. Es hängt auch ein bisschen von der Programmiersprache ab, je "höher" die Programmiersprache, desto mehr Optimierungspotential einsteht.
Relativ simple Optimierungen betreffen die Optimierung von mathematischen Operationen. Beispielsweise kann eine Division (oder Multiplikation) durch 2, 4, 8, 16, usw bei Ganzzahlen durch Bitverschiebung schneller gemacht werden:
x = y / 16 macht ein Compiler zu
x = y >> 4.
Oder manche Rechnungen können bereits zur Kompilizierzeit vorgenommen werden:
pi = 3.14159; u = 2 * pi * r wird zu
u = 6.28318 * r. Sollte Pi in dem Programm später nicht mehr verwendet werden, dann wird der Compiler die Variable eliminieren.
Dann gibt es relativ viele Optimierungen die Schleifen betreffen:
Code:
for (i=0; i < 10; ++i) {
a[i] = 17 * i;
}
Kann der Compiler die (langsame) Multiplikation durch eine schnellere Addition ersetzen:
Code:
tmp = -17;
for (i = 0; i < 10; ++i) {
tmp = tmp + 17;
a[i] = tmp;
}
Gerade bei Schleifen mit fixer Grösser kann der Compiler dann auch die Schleife durch "Copy&Paste" ersetzen ("Loop unwinding"):
Code:
for( i=0 ; i<8 ; i=i+1 ) {
dest[i] = src[i];
}
wird zu
Code:
dest[0] = src[0];
dest[1] = src[1];
dest[2] = src[2];
dest[3] = src[3];
dest[4] = src[4];
dest[5] = src[5];
dest[6] = src[6];
dest[7] = src[7];
Je nach dem kann der Compiler auch Dinge etwas umordnen. Beispielsweise:
Code:
u = (b + c) * x;
v = (b + c) * y;
wird der gemeinsame Teil rausgenommen und nur 1x berechnet, anstatt zweimal:
Code:
tmp = b + c;
u = tmp * x;
v = tmp * y;
Oder eine If-Abfrage in der Schleife die jedes mal das gleich funktioniert kann rausgenommen werden, so dass die Überprüfung nur einmal stattfindet:
Code:
int result = 0;
boolean do_plus = ...;
for( i=0 ; i<100 ; i=i+1 ) {
if(do_plus == true) {
result = result + i;
} else {
result = result - i;
}
}
wird zu
Code:
int result = 0;
boolean do_plus = ...;
if(do_plus == true) {
for( i=0 ; i<100 ; i=i+1 ) {
result = result + i;
}
} else {
for( i=0 ; i<100 ; i=i+1 ) {
result = result - i;
}
}
}
Das sind ein paar (imho) anschauliche Beispiele, hab die meisten aus der Wikipedia geklaut:
https://en.wikipedia.org/wiki/Optimizing_compiler
Die Liste von möglichen Optimierungen ist aber riesig, insbesondere auch wenn es um das Generieren von Maschinen- oder Bytecode geht.
- Auf Intel-Plattformen (x86) gibt es z.B. einen Maschinenbefehl der Addition und Multiplikation um 2,4,8 kombiniert: x = a * 4 + c kann in einem einzigen Assemblerbefehl gemacht werden, anstatt zwei (zuerst Multiplikation, dann Addition).
- Spezielle SSE-Instruktionen können verwendet werden, wenn der Compiler merkt dass die gleiche Opteration mehrmals ausgeführt wird:
Code:
for (i=0; i<8; i++){
a[i] = b[i] + c[i];
}
Anstatt da eine Schleife zu verwenden kann der Compiler da etwa dem Prozessor dank SSE-Instruktionen sagen, er solle alle 8 Rechnungen gleichzeitig ausführen, anstatt eine nach der anderen (nennt sich Schleifen Vektorisierung).
- In Programmiersprachen wie Java kann manchmal die Überprüfung wegfallen, ob ein Zugriff im Array ist, wenn der Compiler garantieren kann, dass dies nicht der Fall ist, so dass weniger Überprüfungscode ausgeführt werden muss.
- In Programmiersprachen mit bekannten Funktionen wie C kann der Compiler auch Funktionen strlen (Länge eines Strings berechnen) bereits zur Kompilierzeit ausführen, strlen("hallo") kann der Compiler ausrechnen dass das 5 ist, weil der String keine Variablen ist.
- Weitere Optimierungen wären etwa das Rauslöschen von Funktionen die niemals aufgerufen werden, oder Schleifen die niemals ausgeführt werden usw.
- In anderen Sprachen wo Rekursion wichtig kann der Compiler auch rekursive Funktionsaufrufe durch "Tail-Call-Optimization" wegoptimieren, damit die so schnell sind wie Schleifen.
Grundsätzlich gilt dass der Compiler häufig auch Dinge optimiert, wo der Mensch auch drauf gekommen wäre, also lieber mal eine Variable mehr verwenden wenn es den Code lesbarer macht.
Nachtrag: Eine leider etwas gar technische, aber kurze Übersicht findet auch in den LLVM-Dokus:
http://llvm.org/docs/Passes.html