Ich habe versucht, extrem leistungskritischen Code (einen schnellen Sortieralgorithmus, der in einer Monte-Carlo-Simulation millionenfach aufgerufen wird) durch Abrollen der Schleife zu optimieren. Hier ist die innere Schleife, die ich zu beschleunigen versuche:
// Search for elements to swap.
while(myArray[++index1] < pivot) {}
while(pivot < myArray[--index2]) {}
Ich habe versucht, mich zu etwas abzuwickeln wie:
while(true) {
if(myArray[++index1] < pivot) break;
if(myArray[++index1] < pivot) break;
// More unrolling
}
while(true) {
if(pivot < myArray[--index2]) break;
if(pivot < myArray[--index2]) break;
// More unrolling
}
Das machte absolut keinen Unterschied, also habe ich es wieder in die besser lesbare Form geändert. Ich habe ähnliche Erfahrungen gemacht, als ich versucht habe, die Schleife abzuwickeln. Wann, wenn überhaupt, ist das Abrollen von Schleifen angesichts der Qualität von Verzweigungsvorhersagen auf moderner Hardware immer noch eine nützliche Optimierung?
Antworten:
Das Abrollen von Schleifen ist sinnvoll, wenn Sie Abhängigkeitsketten unterbrechen können. Dies gibt einer außer Betrieb befindlichen oder superskalaren CPU die Möglichkeit, die Dinge besser zu planen und somit schneller zu laufen.
Ein einfaches Beispiel:
Hier ist die Abhängigkeitskette der Argumente sehr kurz. Wenn Sie einen Stillstand bekommen, weil Sie einen Cache-Fehler im Datenarray haben, kann die CPU nichts anderes tun, als zu warten.
Auf der anderen Seite dieser Code:
könnte schneller laufen. Wenn Sie in einer Berechnung einen Cache-Fehler oder einen anderen Stillstand erhalten, gibt es noch drei weitere Abhängigkeitsketten, die nicht vom Stillstand abhängen. Eine außer Betrieb befindliche CPU kann diese ausführen.
quelle
Diese würden keinen Unterschied machen, da Sie die gleiche Anzahl von Vergleichen durchführen. Hier ist ein besseres Beispiel. Anstatt:
schreiben:
Selbst dann spielt es mit ziemlicher Sicherheit keine Rolle, aber Sie führen jetzt 50 statt 200 Vergleiche durch (stellen Sie sich vor, der Vergleich ist komplexer).
Das manuelle Abrollen von Schleifen im Allgemeinen ist jedoch weitgehend ein Artefakt der Geschichte. Es ist eine weitere wachsende Liste von Dingen, die ein guter Compiler für Sie erledigt, wenn es darauf ankommt. Zum Beispiel machen sich die meisten Leute nicht die Mühe zu schreiben
x <<= 1
oderx += x
stattdessenx *= 2
. Sie schreiben einfachx *= 2
und der Compiler optimiert es für Sie auf das Beste.Grundsätzlich müssen Sie Ihren Compiler immer weniger hinterfragen.
quelle
Unabhängig von der Verzweigungsvorhersage auf moderner Hardware rollen die meisten Compiler die Schleife ohnehin für Sie ab.
Es lohnt sich herauszufinden, wie viele Optimierungen Ihr Compiler für Sie vornimmt.
Ich fand Felix von Leitners Präsentation zu diesem Thema sehr aufschlussreich. Ich empfehle Ihnen, es zu lesen. Zusammenfassung: Moderne Compiler sind SEHR clever, daher sind Handoptimierungen fast nie effektiv.
quelle
Soweit ich weiß, entrollen moderne Compiler gegebenenfalls bereits Schleifen - ein Beispiel ist gcc. Wenn die Optimierungsflags übergeben werden, heißt es im Handbuch:
In der Praxis ist es also wahrscheinlich, dass Ihr Compiler die trivialen Fälle für Sie erledigt. Es liegt daher an Ihnen, sicherzustellen, dass der Compiler so viele Ihrer Schleifen wie möglich leicht bestimmen kann, wie viele Iterationen benötigt werden.
quelle
Das Abrollen von Schleifen, sei es das Abrollen von Hand oder das Abrollen von Compilern, kann häufig kontraproduktiv sein, insbesondere bei neueren x86-CPUs (Core 2, Core i7). Fazit: Benchmarking Ihres Codes mit und ohne Schleifenabwicklung auf den CPUs, auf denen Sie diesen Code bereitstellen möchten.
quelle
Versuchen ohne es zu wissen ist nicht der richtige Weg.
Nimmt diese Art einen hohen Prozentsatz der Gesamtzeit in Anspruch?
Alles, was das Abrollen der Schleife bewirkt, ist das Reduzieren des Schleifenaufwands durch Inkrementieren / Dekrementieren, Vergleichen für die Stoppbedingung und Springen. Wenn das, was Sie in der Schleife tun, mehr Befehlszyklen benötigt als der Schleifen-Overhead selbst, werden Sie prozentual keine große Verbesserung feststellen.
Hier ist ein Beispiel, wie Sie maximale Leistung erzielen.
quelle
Das Abrollen der Schleife kann in bestimmten Fällen hilfreich sein. Der einzige Vorteil ist, einige Tests nicht zu überspringen!
Es kann zum Beispiel das Ersetzen von Skalaren und das effiziente Einfügen von Software-Prefetching ermöglichen ... Sie wären überrascht, wie nützlich es sein kann (Sie können die meisten Loops sogar mit -O3 leicht um 10% beschleunigen), indem Sie es aggressiv abrollen.
Wie bereits gesagt, hängt es stark von der Schleife ab und der Compiler und das Experiment sind notwendig. Es ist schwer, eine Regel zu erstellen (oder die Compiler-Heuristik zum Abrollen wäre perfekt).
quelle
Das Abrollen der Schleife hängt vollständig von Ihrer Problemgröße ab. Es hängt ganz davon ab, ob Ihr Algorithmus die Größe in kleinere Arbeitsgruppen reduzieren kann. Was Sie oben getan haben, sieht nicht so aus. Ich bin mir nicht sicher, ob eine Monte-Carlo-Simulation überhaupt abgewickelt werden kann.
Ein gutes Szenario für das Abrollen der Schleife wäre das Drehen eines Bildes. Da könnte man separate Arbeitsgruppen drehen. Damit dies funktioniert, müssten Sie die Anzahl der Iterationen reduzieren.
quelle
Das Abrollen der Schleife ist immer noch nützlich, wenn sowohl in als auch mit der Schleife viele lokale Variablen vorhanden sind. Um diese Register mehr wiederzuverwenden, anstatt eines für den Schleifenindex zu speichern.
In Ihrem Beispiel verwenden Sie eine kleine Anzahl lokaler Variablen, ohne die Register zu überbeanspruchen.
Der Vergleich (zum Schleifenende) ist auch ein Hauptnachteil, wenn der Vergleich schwer ist (dh keine
test
Anweisung), insbesondere wenn er von einer externen Funktion abhängt.Das Abrollen der Schleife erhöht auch das Bewusstsein der CPU für die Verzweigungsvorhersage, aber diese treten trotzdem auf.
quelle