Der Compiler nimmt Ihren Code, teilt ihn in sehr einfache Anweisungen auf und kombiniert und ordnet sie dann so an, dass er sie für optimal hält.
Der Code
int i = 1;
int x = ++i + ++i;
besteht aus folgenden Anweisungen:
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
5. read i as tmp2
6. read i as tmp3
7. add 1 to tmp3
8. store tmp3 in i
9. read i as tmp4
10. add tmp2 and tmp4, as tmp5
11. store tmp5 in x
Aber obwohl dies eine nummerierte Liste ist, wie ich sie geschrieben habe, gibt es hier nur wenige Ordnungsabhängigkeiten : 1-> 2-> 3-> 4-> 5-> 10-> 11 und 1-> 6-> 7- > 8-> 9-> 10-> 11 müssen in ihrer relativen Reihenfolge bleiben. Ansonsten kann der Compiler frei neu ordnen und möglicherweise Redundanz beseitigen.
Sie können die Liste beispielsweise folgendermaßen bestellen:
1. store 1 in i
2. read i as tmp1
6. read i as tmp3
3. add 1 to tmp1
7. add 1 to tmp3
4. store tmp1 in i
8. store tmp3 in i
5. read i as tmp2
9. read i as tmp4
10. add tmp2 and tmp4, as tmp5
11. store tmp5 in x
Warum kann der Compiler das tun? Weil es keine Sequenzierung zu den Nebenwirkungen des Inkrements gibt. Aber jetzt kann der Compiler vereinfachen: Zum Beispiel gibt es in 4 einen toten Speicher: Der Wert wird sofort überschrieben. Auch tmp2 und tmp4 sind wirklich dasselbe.
1. store 1 in i
2. read i as tmp1
6. read i as tmp3
3. add 1 to tmp1
7. add 1 to tmp3
8. store tmp3 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
Und jetzt ist alles, was mit tmp1 zu tun hat, toter Code: Er wird nie verwendet. Und das erneute Lesen von i kann auch beseitigt werden:
1. store 1 in i
6. read i as tmp3
7. add 1 to tmp3
8. store tmp3 in i
10. add tmp3 and tmp3, as tmp5
11. store tmp5 in x
Schauen Sie, dieser Code ist viel kürzer. Der Optimierer freut sich. Der Programmierer ist nicht, weil ich nur einmal erhöht wurde. Hoppla.
Schauen wir uns stattdessen etwas anderes an, das der Compiler tun kann: Kehren wir zur Originalversion zurück.
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
5. read i as tmp2
6. read i as tmp3
7. add 1 to tmp3
8. store tmp3 in i
9. read i as tmp4
10. add tmp2 and tmp4, as tmp5
11. store tmp5 in x
Der Compiler könnte es folgendermaßen neu anordnen:
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
6. read i as tmp3
7. add 1 to tmp3
8. store tmp3 in i
5. read i as tmp2
9. read i as tmp4
10. add tmp2 and tmp4, as tmp5
11. store tmp5 in x
und dann wieder bemerken, dass ich zweimal gelesen werde, also eliminiere einen von ihnen:
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
6. read i as tmp3
7. add 1 to tmp3
8. store tmp3 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
Das ist schön, aber es kann noch weiter gehen: Es kann tmp1 wiederverwenden:
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
6. read i as tmp1
7. add 1 to tmp1
8. store tmp1 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
Dann kann das erneute Lesen von i in 6 beseitigt werden:
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
7. add 1 to tmp1
8. store tmp1 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
Jetzt ist 4 ein toter Laden:
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
7. add 1 to tmp1
8. store tmp1 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
und jetzt können 3 und 7 zu einer Anweisung zusammengeführt werden:
1. store 1 in i
2. read i as tmp1
3+7. add 2 to tmp1
8. store tmp1 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
Beseitigen Sie die letzte temporäre:
1. store 1 in i
2. read i as tmp1
3+7. add 2 to tmp1
8. store tmp1 in i
10. add tmp1 and tmp1, as tmp5
11. store tmp5 in x
Und jetzt erhalten Sie das Ergebnis, das Visual C ++ Ihnen liefert.
Beachten Sie, dass in beiden Optimierungspfaden die wichtigen Ordnungsabhängigkeiten beibehalten wurden, sofern die Anweisungen nicht entfernt wurden, um nichts zu tun.