Ich habe einige Teile des Linux-Kernels durchsucht und Aufrufe wie diesen gefunden:
if (unlikely(fd < 0))
{
/* Do something */
}
oder
if (likely(!err))
{
/* Do something */
}
Ich habe die Definition von ihnen gefunden:
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
Ich weiß, dass sie zur Optimierung dienen, aber wie funktionieren sie? Und wie viel Leistungs- / Größenabnahme kann von ihrer Verwendung erwartet werden? Und ist es den Aufwand wert (und wahrscheinlich die Portabilität zu verlieren), zumindest im Engpasscode (natürlich im Userspace).
BOOST_LIKELY
__builtin_expect
auf eine andere Frage.#define likely(x) (x)
und#define unlikely(x) (x)
auf Plattformen tun, die diese Art von Hinweisen nicht unterstützen.Antworten:
Sie weisen den Compiler an, Anweisungen auszugeben, die dazu führen, dass die Verzweigungsvorhersage die "wahrscheinliche" Seite einer Sprunganweisung begünstigt. Dies kann ein großer Gewinn sein. Wenn die Vorhersage korrekt ist, bedeutet dies, dass die Sprunganweisung grundsätzlich frei ist und keine Zyklen benötigt. Wenn andererseits die Vorhersage falsch ist, bedeutet dies, dass die Prozessor-Pipeline geleert werden muss und mehrere Zyklen kosten kann. Solange die Vorhersage die meiste Zeit korrekt ist, ist dies in der Regel gut für die Leistung.
Wie bei allen derartigen Leistungsoptimierungen sollten Sie dies erst nach einer umfassenden Profilerstellung tun, um sicherzustellen, dass sich der Code tatsächlich in einem Engpass befindet, und wahrscheinlich aufgrund der Mikronatur, dass er in einer engen Schleife ausgeführt wird. Im Allgemeinen sind die Linux-Entwickler ziemlich erfahren, daher würde ich mir vorstellen, dass sie das getan hätten. Sie kümmern sich nicht wirklich um Portabilität, da sie nur auf gcc abzielen, und sie haben eine sehr genaue Vorstellung von der Assembly, die sie generieren sollen.
quelle
"[...]that it is being run in a tight loop"
verfügen viele CPUs über einen Verzweigungsprädiktor. Die Verwendung dieser Makros hilft daher nur beim ersten Ausführen von Code oder wenn die Verlaufstabelle von einer anderen Verzweigung mit demselben Index in die Verzweigungstabelle überschrieben wird. In einer engen Schleife und unter der Annahme, dass eine Verzweigung die meiste Zeit in eine Richtung verläuft, wird der Verzweigungsprädiktor wahrscheinlich sehr schnell beginnen, die richtige Verzweigung zu erraten. - Dein Freund in Pedanterie.Lassen Sie uns dekompilieren, um zu sehen, was GCC 4.8 damit macht
Ohne
__builtin_expect
Kompilieren und dekompilieren Sie mit GCC 4.8.2 x86_64 Linux:
Ausgabe:
Die Befehlsreihenfolge im Speicher blieb unverändert: zuerst die
printf
und dannputs
und dieretq
Rückgabe.Mit
__builtin_expect
Ersetzen Sie nun durch
if (i)
:und wir bekommen:
Das
printf
(kompiliert zu__printf_chk
) wurde nachputs
und nach der Rückkehr an das Ende der Funktion verschoben, um die Verzweigungsvorhersage zu verbessern, wie in anderen Antworten erwähnt.Es ist also im Grunde dasselbe wie:
Diese Optimierung wurde nicht durchgeführt
-O0
.Aber viel Glück beim Schreiben eines Beispiels, das mit und
__builtin_expect
ohne schneller läuft. CPUs sind heutzutage wirklich schlau . Meine naiven Versuche sind hier .C ++ 20
[[likely]]
und[[unlikely]]
C ++ 20 hat diese C ++ - Integrationen standardisiert: Verwendung des wahrscheinlichen / unwahrscheinlichen Attributs von C ++ 20 in der if-else-Anweisung Sie werden wahrscheinlich (ein Wortspiel!) Dasselbe tun.
quelle
Dies sind Makros, die dem Compiler Hinweise geben, in welche Richtung ein Zweig gehen kann. Die Makros werden auf GCC-spezifische Erweiterungen erweitert, sofern diese verfügbar sind.
GCC verwendet diese, um die Verzweigungsvorhersage zu optimieren. Zum Beispiel, wenn Sie so etwas wie das Folgende haben
Dann kann es diesen Code so umstrukturieren, dass er eher so aussieht wie:
Dies hat den Vorteil, dass der Prozessor beim ersten Verzweigen einen erheblichen Overhead verursacht, da er möglicherweise spekulativ Code geladen und weiter ausgeführt hat. Wenn es feststellt, dass es den Zweig nehmen wird, muss es diesen ungültig machen und am Zweigziel beginnen.
Die meisten modernen Prozessoren haben jetzt eine Art Verzweigungsvorhersage, aber das hilft nur, wenn Sie die Verzweigung zuvor durchlaufen haben und sich die Verzweigung noch im Verzweigungsvorhersage-Cache befindet.
Es gibt eine Reihe anderer Strategien, die der Compiler und der Prozessor in diesen Szenarien verwenden können. Weitere Informationen zur Funktionsweise von Zweigprädiktoren finden Sie unter Wikipedia: http://en.wikipedia.org/wiki/Branch_predictor
quelle
goto
s gemacht werden, ohne das zu wiederholenreturn x
: stackoverflow.com/a/31133787/895245Sie veranlassen den Compiler, die entsprechenden Verzweigungshinweise auszugeben, wo die Hardware sie unterstützt. Dies bedeutet normalerweise nur, ein paar Bits im Befehls-Opcode zu drehen, damit sich die Codegröße nicht ändert. Die CPU beginnt mit dem Abrufen von Anweisungen vom vorhergesagten Speicherort, spült die Pipeline und beginnt von vorne, wenn sich herausstellt, dass dies bei Erreichen der Verzweigung falsch ist. Wenn der Hinweis korrekt ist, wird der Zweig dadurch viel schneller - genau wie viel schneller, hängt von der Hardware ab. und wie sehr dies die Leistung des Codes beeinflusst, hängt davon ab, welcher Anteil des Zeithinweises korrekt ist.
Beispielsweise kann auf einer PowerPC-CPU ein nicht angezeigter Zweig 16 Zyklen dauern, ein korrekt angedeuteter 8 und ein falsch angedeuteter 24. In innersten Schleifen kann ein guter Hinweis einen enormen Unterschied machen.
Portabilität ist nicht wirklich ein Problem - vermutlich befindet sich die Definition in einem plattformübergreifenden Header. Sie können einfach "wahrscheinlich" und "unwahrscheinlich" für Plattformen definieren, die keine statischen Verzweigungshinweise unterstützen.
quelle
Dieses Konstrukt teilt dem Compiler mit, dass der Ausdruck EXP höchstwahrscheinlich den Wert C hat. Der Rückgabewert ist EXP. __builtin_expect soll in einem bedingten Ausdruck verwendet werden. In fast allen Fällen wird es im Zusammenhang mit booleschen Ausdrücken verwendet. In diesem Fall ist es viel bequemer, zwei Hilfsmakros zu definieren:
Diese Makros können dann wie in verwendet werden
Referenz: https://www.akkadia.org/drepper/cpumemory.pdf
quelle
__builtin_expect(!!(expr),0)
statt nur verwenden__builtin_expect((expr),0)
?!!
ist gleichbedeutend mit dem Casting von etwas zu abool
. Manche Leute schreiben es gerne so.(allgemeiner Kommentar - andere Antworten decken die Details ab)
Es gibt keinen Grund, warum Sie die Portabilität verlieren sollten, wenn Sie sie verwenden.
Sie haben immer die Möglichkeit, ein einfaches Inline- oder Makro mit Null-Effekt zu erstellen, mit dem Sie auf anderen Plattformen mit anderen Compilern kompilieren können.
Sie profitieren einfach nicht von der Optimierung, wenn Sie sich auf anderen Plattformen befinden.
quelle
Laut dem Kommentar von Cody hat dies nichts mit Linux zu tun, sondern ist ein Hinweis auf den Compiler. Was passiert, hängt von der Architektur und der Compilerversion ab.
Diese spezielle Funktion unter Linux wird in Treibern etwas missbraucht. Wie osgx in der Semantik des Hot-Attributs hervorhebt , kann jede
hot
oder jedecold
Funktion, die in einem Block aufgerufen wird, automatisch darauf hinweisen, dass die Bedingung wahrscheinlich ist oder nicht. Zum Beispieldump_stack()
ist markiert,cold
so dass dies redundant ist,Zukünftige Versionen von
gcc
können eine Funktion basierend auf diesen Hinweisen selektiv inline einbinden. Es gab auch Vorschläge, dass dies nicht der Fall istboolean
, aber eine Punktzahl wie höchstwahrscheinlich usw. Im Allgemeinen sollte es bevorzugt werden, einen alternativen Mechanismus wie zu verwendencold
. Es gibt keinen Grund, es an einem anderen Ort als auf heißen Wegen zu verwenden. Was ein Compiler auf einer Architektur macht, kann auf einer anderen völlig anders sein.quelle
In vielen Linux-Versionen finden Sie complier.h in / usr / linux /. Sie können es einfach zur Verwendung hinzufügen. Und eine andere Meinung, unwahrscheinlich (), ist eher nützlich als wahrscheinlich (), weil
es kann auch in vielen Compilern optimiert werden.
Übrigens, wenn Sie das Detailverhalten des Codes beobachten möchten, können Sie einfach Folgendes tun:
Dann öffnen Sie obj.s, Sie können die Antwort finden.
quelle
Sie sind Hinweise für den Compiler, um die Hinweispräfixe für Zweige zu generieren. Unter x86 / x64 belegen sie ein Byte, sodass Sie für jeden Zweig höchstens eine Erhöhung um ein Byte erhalten. Die Leistung hängt vollständig von der Anwendung ab. In den meisten Fällen ignoriert der Verzweigungsprädiktor auf dem Prozessor diese heutzutage.
Bearbeiten: Ich habe einen Ort vergessen, an dem sie wirklich helfen können. Dadurch kann der Compiler das Kontrollflussdiagramm neu anordnen, um die Anzahl der für den "wahrscheinlichen" Pfad verwendeten Zweige zu verringern. Dies kann zu einer deutlichen Verbesserung der Schleifen führen, in denen Sie mehrere Exit-Fälle prüfen.
quelle
Dies sind GCC-Funktionen, mit denen der Programmierer dem Compiler einen Hinweis darauf geben kann, welche Verzweigungsbedingung in einem bestimmten Ausdruck am wahrscheinlichsten ist. Auf diese Weise kann der Compiler die Verzweigungsbefehle so erstellen, dass im häufigsten Fall die geringste Anzahl von Befehlen ausgeführt wird.
Wie die Verzweigungsbefehle aufgebaut sind, hängt von der Prozessorarchitektur ab.
quelle