Pentium 4 (auch bekannt als Netburst-Mikroarchitektur) hatte Verzweigungsvorhersagen als Präfixe für die jcc-Anweisungen, aber nur P4 hat jemals etwas damit gemacht. Siehe http://ref.x86asm.net/geek32.html . Und
Abschnitt 3.5 von Agner Fogs ausgezeichnetem asm opt-Leitfaden von http://www.agner.org/optimize/ . Er hat auch eine Anleitung zur Optimierung in C ++.
Frühere und spätere x86-CPUs ignorieren diese Präfixbytes stillschweigend. Gibt es Leistungstestergebnisse für die Verwendung wahrscheinlicher / unwahrscheinlicher Hinweise? erwähnt, dass PowerPC einige Sprunganweisungen hat, die einen Verzweigungsvorhersagehinweis als Teil der Codierung enthalten. Es ist ein ziemlich seltenes architektonisches Merkmal. Die statische Vorhersage von Zweigen zur Kompilierungszeit ist sehr schwierig, daher ist es normalerweise besser, sie der Hardware zu überlassen, um dies herauszufinden.
Es wird offiziell nicht viel darüber veröffentlicht, wie sich die Verzweigungsprädiktoren und Verzweigungszielpuffer in den neuesten Intel- und AMD-CPUs genau verhalten. Die Optimierungshandbücher (leicht zu finden auf den Websites von AMD und Intel) geben einige Ratschläge, dokumentieren jedoch kein spezifisches Verhalten. Einige Leute haben Tests durchgeführt, um zu versuchen, die Implementierung zu erraten, z. B. wie viele BTB-Einträge Core2 hat ... Wie auch immer, die Idee, den Prädiktor explizit anzudeuten, wurde (vorerst) aufgegeben.
Es ist beispielsweise dokumentiert, dass Core2 über einen Zweigverlaufspuffer verfügt, der eine Fehlvorhersage des Schleifenausgangs vermeiden kann, wenn die Schleife immer eine konstant kurze Anzahl von Iterationen <8 oder 16 IIRC ausführt. Aber seien Sie nicht zu schnell zum Abrollen, da eine Schleife, die in 64 Byte (oder 19 Ups auf Penryn) passt, keine Engpässe beim Abrufen von Anweisungen aufweist, da sie aus einem Puffer wiedergegeben wird. Lesen Sie die PDFs von Agner Fog. Sie sind ausgezeichnet .
Siehe auch Warum hat Intel in diesen Jahren den Mechanismus zur Vorhersage statischer Zweige geändert? : Intel verwendet seit Sandybridge überhaupt keine statische Vorhersage, soweit wir aus Leistungsexperimenten ersehen können, die versuchen, die Funktionsweise von CPUs rückzuentwickeln. (Viele ältere CPUs haben eine statische Vorhersage als Fallback, wenn die dynamische Vorhersage fehlschlägt. Die normale statische Vorhersage besteht darin, dass Vorwärtsverzweigungen nicht und Rückwärtsverzweigungen verwendet werden (da Rückwärtsverzweigungen häufig Schleifenverzweigungen sind).)
Die Wirkung von likely()
/ unlikely()
Makros unter Verwendung von GNU __builtin_expect
Cs (wie in Drakoshas Antwort erwähnt) fügt BP-Hinweise nicht direkt in den Asm ein . (Möglicherweise mit gcc -march=pentium4
, aber nicht beim Kompilieren für etwas anderes).
Der eigentliche Effekt besteht darin, den Code so auszulegen, dass auf dem schnellen Pfad weniger Verzweigungen und möglicherweise insgesamt weniger Anweisungen vorhanden sind. Dies hilft bei der Verzweigungsvorhersage in Fällen, in denen die statische Vorhersage ins Spiel kommt (z. B. sind dynamische Prädiktoren kalt, auf CPUs, die auf die statische Vorhersage zurückgreifen, anstatt nur Verzweigungen in den Prädiktor-Caches miteinander aliasen zu lassen).
Siehe Was ist der Vorteil von GCCs __builtin_expect in if else-Anweisungen? für ein bestimmtes Beispiel von Code-Gen.
Entnommene Zweige kosten etwas mehr als nicht genommene Zweige, selbst wenn sie perfekt vorhergesagt werden. Wenn die CPU Code in Blöcken von 16 Bytes abruft, um ihn parallel zu decodieren, bedeutet eine genommene Verzweigung, dass spätere Befehle in diesem Abrufblock nicht Teil des auszuführenden Befehlsstroms sind. Es entstehen Blasen im Front-End, die zu einem Engpass im Code mit hohem Durchsatz werden können (der bei Cache-Fehlern nicht im Back-End blockiert und eine hohe Parallelität auf Befehlsebene aufweist).
Das Herumspringen zwischen verschiedenen Blöcken berührt möglicherweise auch mehr Cache-Codezeilen , erhöht den L1i-Cache-Footprint und verursacht möglicherweise mehr Befehls-Cache-Fehler, wenn es kalt ist. (Und möglicherweise UOP-Cache-Footprint). Das ist ein weiterer Vorteil, wenn der schnelle Weg kurz und linear ist.
Die profilgesteuerte Optimierung von GCC macht normalerweise wahrscheinliche / unwahrscheinliche Makros unnötig. Der Compiler sammelt Laufzeitdaten darüber, wie jeder Zweig Code-Layout-Entscheidungen getroffen und heiße oder kalte Blöcke / Funktionen identifiziert hat. (z. B. werden Schleifen in heißen Funktionen, aber nicht in kalten Funktionen abgewickelt.) Siehe -fprofile-generate
und -fprofile-use
im GCC-Handbuch . Wie verwende ich profilgesteuerte Optimierungen in g ++?
Andernfalls muss GCC verschiedene Heuristiken erraten, wenn Sie keine wahrscheinlichen / unwahrscheinlichen Makros und kein PGO verwendet haben. -fguess-branch-probability
ist standardmäßig bei -O1
und höher aktiviert .
https://www.phoronix.com/scan.php?page=article&item=gcc-82-pgo&num=1 bietet Benchmark-Ergebnisse für PGO im Vergleich zu regulären mit gcc8.2 auf einer Xeon Scalable Server-CPU. (Skylake-AVX512). Jeder Benchmark wurde mindestens geringfügig beschleunigt, und einige profitierten von ~ 10%. (Das meiste davon ist wahrscheinlich auf das Abrollen von Schleifen in Hot-Loops zurückzuführen, aber ein Teil davon ist vermutlich auf ein besseres Zweiglayout und andere Effekte zurückzuführen.)