Wie kann das wahrscheinlich/unwahrscheinlich-Makros in Linux-kernel arbeiten, und was ist Ihr nutzen?
Ich habe gegraben durch einige Teile des Linux-Kernels, und fand Aufrufe wie diese:
if (unlikely(fd < 0))
{
/* Do something */
}
oder
if (likely(!err))
{
/* Do something */
}
Fand ich die definition von Ihnen:
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
Ich weiß, Sie sind für die Optimierung, aber wie funktionieren Sie? Und wie viel Leistung/Größe verringern kann erwartet werden, aus mit Ihnen? Und lohnt sich der Aufwand (und Verlust der Portabilität wahrscheinlich) zumindest in Engpass-code (im userspace, natürlich).
- Das ist wirklich nicht spezifisch für den Linux-kernel oder über Makros, aber eine compiler-Optimierung. Sollte dies retagged zu reflektieren?
- Das Papier Was jeder Programmierer wissen sollte, über das Gedächtnis (S. 57) enthält eine ausführliche Erläuterung.
- Nach der kernelnewbies FAQ (und die neueste 3.11 kernel-source), die makro-Definitionen sind etwas anders jetzt: #define rechnen(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) ich denke, dies fügt ein wenig mehr zur Verwirrung!? 🙂 Ich verstehe nicht die Notwendigkeit für doppelte NICHT (<code>!!</code>).
- siehe auch
BOOST_LIKELY
- Verwandte: eine benchmark, die auf die Verwendung von
__builtin_expect
auf eine andere Frage. - Es gibt keine Portabilität Problem. Sie können trivial Dinge tun, wie
#define likely(x) (x)
und#define unlikely(x) (x)
auf Plattformen, die keine Unterstützung für diese Art des hinting.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Sind Sie Hinweis an den compiler zu emittieren Anweisungen, die bewirken, branch prediction zu Gunsten der "wahrscheinlich" - Seite einer jump-Anweisung. Dies kann ein großer Gewinn, wenn die Vorhersage richtig ist, bedeutet es, dass die jump-Anweisung ist grundsätzlich kostenlos und wird null Zyklen. Auf der anderen Seite, wenn die Vorhersage falsch ist, dann bedeutet es, die Prozessor-pipeline muss geleert werden, und es können Kosten mehrere Zyklen. So lange, wie die Vorhersage richtig ist, die meisten der Zeit, dies wird dazu neigen, die gut für die Leistung.
Wie alle derartigen performance-Optimierungen sollten Sie nur tun, es nach einer umfangreichen profiling, um sicherzustellen, der code ist wirklich in einem Engpass, und wahrscheinlich angesichts der Mikro-Natur, dass es ausgeführt wird in einer engen Schleife. In der Regel die Linux-Entwickler sind ziemlich erfahren, also ich könnte mir vorstellen, Sie würden das getan haben. Es kümmert Sie nicht wirklich zu viel über Beweglichkeit, wie Sie nur Ziel-gcc, und Sie haben eine sehr enge Vorstellung von der assembly, die Sie wollen, zu generieren.
"[...]that it is being run in a tight loop"
viele CPUs haben eine branch predictor, so mit diesen Makros hilft nur das erste mal-code ausgeführt wird oder wenn die history-Tabelle überschrieben wird durch eine andere Filiale mit dem gleichen index in den Verzweigungen der Tabelle. In einer engen Schleife, und unter der Annahme einer Verzweigung geht ein Weg, die meisten der Zeit, die Zweig-predictor wird wahrscheinlich beginnen, zu erraten, die richtige Filiale sehr schnell. - dein Freund in Pedanterie.the history table is overwritten by a different branch with the same index into the branching table.
ich war nur darauf aus den engen Schleife Sache. Wenn Sie ausführen, die Schleife über und über, die anfänglichen Kosten ist trivial, und die branch-predictor übernimmt, es sei denn, Sie bekommen, branch prediction Prügel durch springen/Anrufe innerhalb des "engen loop". Sagen, die der compiler für einen Zweig ist ein Mikro-Optimierung in engen Schleifen laufen viele, viele Male. Alle sehr pedantisch, um sicher zu sein 🙂Let ' s dekompilieren um zu sehen, was der GCC 4.8 mit ihm macht
Ohne
__builtin_expect
Kompilieren und dekompilieren mit GCC 4.8.2 x86_64-Linux:
Ausgabe:
Den Unterricht, um in Erinnerung blieb unverändert: die ersten
printf
und dannputs
und dieretq
zurück.Mit
__builtin_expect
Nun ersetzen
if (i)
mit:und wir erhalten:
Den
printf
(kompiliert__printf_chk
) wurde verschoben, um das Ende der Funktion, nachputs
und die Rendite zu verbessern, branch prediction wie erwähnt von anderen Antworten.Also es ist im Grunde das gleiche wie:
Diese Optimierung war nicht mit
-O0
.Aber viel Glück, auf das schreiben ein Beispiel, die schneller ausgeführt wird mit
__builtin_expect
als ohne, CPUs sind wirklich smart diese Tage. Meine naive versuche hier.Diese sind Makros, die Hinweise an den compiler über den Weg, eine Filiale gehen kann. Die Makros expandieren zu GCC spezifische Erweiterungen, wenn Sie verfügbar sind.
GCC nutzt diese, um zur Optimierung für die branch prediction. Zum Beispiel, wenn Sie etwas wie das folgende
Dann neu strukturieren kann dieser code so etwas wie:
Der Vorteil dabei ist, dass, wenn der Prozessor nimmt einen Zweig der ersten Zeit, es ist zu einem erheblichen Mehraufwand, da es sich möglicherweise spekulativ laden und ausführen von code weiter. Wenn es bestimmt wird, nehmen Sie den Zweig, dann ist es ungültig, und starten Sie auf dem Zweig target.
Meisten modernen Prozessoren haben jetzt eine Art von branch prediction, aber das nur hilft, wenn Sie habe durch das Zweig vor, und der Zweig ist noch in der branch prediction cache.
Gibt es eine Reihe von anderen Strategien, die die compiler und Prozessor können in diesen Szenarien. Sie finden weitere details auf, wie Zweig Prädiktoren Arbeit bei Wikipedia: http://en.wikipedia.org/wiki/Branch_predictor
goto
s ohne Wiederholung derreturn x
: stackoverflow.com/a/31133787/895245Verursachen Sie der compiler wieder den entsprechenden Zweig Hinweise, wo die hardware Sie unterstützt. Diese Regel bedeutet nur, drehte ein paar bits in the instruction opcode, also code-Größe nicht ändern. Die CPU wird beim starten abrufen von Anweisungen aus der vorhergesagten Position und Spülen Sie die Rohrleitung und von vorn beginnen, wenn sich das als falsch herausstellt, wenn die Branche erreicht ist; in dem Fall, wo der Hinweis korrekt ist, wird der Zweig sehr viel schneller - genau wie viel schneller wird, hängt von der hardware und wie viel dies wirkt sich auf die Leistung des Codes wird davon abhängen, welchen Anteil der Zeit, die Tipp richtig ist.
Beispielsweise auf einer PowerPC-CPU ein unhinted Zweig nehmen könnte, 16 Zyklen, ein richtig angedeutet, eine 8 und eine falsch deutete eine 24. In innersten Schleifen gutes hinting kann einen gewaltigen Unterschied machen.
Portabilität ist nicht wirklich ein Problem - vermutlich die definition ist in einer pro-Plattform-header; Sie können einfach definieren, "wahrscheinlich" und "unwahrscheinlich" nichts für Plattformen, die keine Unterstützung für statische Zweig-Tipps.
Diese Anweisung teilt dem compiler mit, dass der Ausdruck EXP
wahrscheinlich haben Sie den Wert C. Der Rückgabewert EXP.
__builtin_expect verwendet werden soll in einer bedingten
Ausdruck. In fast allen Fällen wird es sich im
Kontext von boolschen Ausdrücken, in dem Fall ist es viel
bequemer definieren zwei Helfer Makros:
Diese Makros können dann verwendet werden, wie in
Referenz: https://www.akkadia.org/drepper/cpumemory.pdf
__builtin_expect(!!(expr),0)
statt nur__builtin_expect((expr),0)
?!!
ist äquivalent zu Gießen, was zu einembool
. Einige Leute mögen schreiben es auf diese Weise.(allgemein Kommentar andere Antworten decken die details)
Gibt es keinen Grund, dass Sie sollten verlieren Portabilität durch die Verwendung von Ihnen.
Haben Sie immer die option zum erstellen eines einfachen null-Effekt "inline" oder ein makro, das Ihnen erlaubt, zu kompilieren auf anderen Plattformen mit anderen Compilern.
Sie nicht nur die Vorteile der Optimierung, wenn Sie auf anderen Plattformen.
Als pro den Kommentar von Cody, das hat nichts mit Linux zu tun, sondern ist ein Hinweis an den compiler. Was passiert, hängt von der Architektur und compiler-version.
Diese Besonderheit in Linux ist etwas mis-Treiber verwendet. Als osgx Punkte in Semantik von hot-Attribut, jede
hot
odercold
- Funktion aufgerufen, wobei in einem block können automatisch Hinweis, dass die Bedingung wahrscheinlich ist oder nicht. Zum Beispieldump_stack()
gekennzeichnet istcold
also das ist redundant,Zukünftigen Versionen von
gcc
wahlweise inline-Funktion auf der Grundlage dieser Hinweise. Es wurden auch Vorschläge, dass es nichtboolean
, aber eine Partitur, wie in wahrscheinlich, etc. Im Allgemeinen ist es vorzuziehen, verwenden einige alternativen Mechanismus wiecold
. Es gibt keinen Grund, es in jedem Ort, aber heiß Pfade. Was für ein compiler, der auf einer Architektur kann dabei völlig unterschiedlich sein auf andere.In vielen linux-Version, finden Sie complier.h in /usr/linux/, Sie können es für Gebrauch einfach. Und eine andere Meinung, unwahrscheinlich() eher nützlich eher als wahrscheinlich (ist), weil
optimiert werden kann als auch in vielen compiler.
Und durch die Art und Weise, wenn Sie wollen, beachten Sie die detail-Verhalten des Codes, die Sie tun können, einfach wie folgt:
Öffnen Sie dann obj.s, finden Sie die Antwort.
Sie sind Hinweise an den compiler zum generieren der Hinweis Präfixe auf den Zweigen. Auf x86/x64, nimmt Sie nur ein byte, also kriegst du am meisten ein ein-byte-Erhöhung für jeden Zweig. Wie für Leistung, es hängt ganz von der Anwendung-in den meisten Fällen, die Zweig-predictor auf dem Prozessor ignoriert Sie, diese Tage.
Edit: Vergaß ein Ort, den Sie kann wirklich helfen. Es kann damit der compiler die Reihenfolge der control-flow-graph zur Verringerung der Zahl der Filialen für das "wahrscheinlich" Weg. Dies kann eine deutliche Verbesserung in Schleifen, wo Sie überprüft haben mehrere exit-Fällen.
Diese sind GCC-Funktionen für den Programmierer zu geben, einen Hinweis an den compiler, was die wahrscheinlichste Verzweigung Bedingung in einer bestimmten Ausdruck. Dies ermöglicht dem compiler die branch-Instruktionen so, dass der häufigste Fall nimmt die geringste Anzahl von Anweisungen ausführen.
Wie der Zweig Anweisungen gebaut werden, sind abhängig von der Prozessor-Architektur.