C/C++-Optimierung entfernt Prüfungen, um zu sehen, ob eine Funktion bereits zuvor ausgeführt wurde
Lesezeit: 8 Minuten
umps
Angenommen, Sie haben eine Funktion in C/C++, die sich beim ersten Ausführen auf eine bestimmte Weise verhält. Und dann verhält es sich zu allen anderen Zeiten anders (siehe unten zum Beispiel). Nachdem es das erste Mal ausgeführt wurde, wird die if-Anweisung überflüssig und könnte wegoptimiert werden, wenn Geschwindigkeit wichtig ist. Gibt es eine Möglichkeit, diese Optimierung vorzunehmen?
bool val = true;
void function1() {
if (val == true) {
// do something
val = false;
}
else {
// do other stuff, val is never set to true again
}
}
Dies liegt im Bereich des selbstmodifizierenden Codes. Ich bezweifle, dass Sie es direkt in c oder c++ tun können
– Bwmat
19. Oktober 2012 um 21:50 Uhr
sicher kannst du. static local boolean teilt dem Compiler mit, dass ein bestimmtes Bit nur einmal ausgeführt werden soll.
– Muhende Ente
20. Oktober 2012 um 0:10 Uhr
Wenn dies für eine Laptop- / Desktop-CPU gilt, lautet die Antwort “Es spielt keine Rolle, da höchstens eine Handvoll Sprünge von jeder derzeit verwendeten CPU falsch vorhergesagt werden, wobei jeder ~ 1 ns verschwendet”.
– j_random_hacker
20. Oktober 2012 um 1:07 Uhr
@j_random_hacker Es gibt jedoch Mikrocontroller, ICs, mobile Geräte usw., bei denen selbst ein einziger Zyklus möglicherweise nicht schön zu verlieren ist. µCs neigen auch dazu, keine Verzweigungsvorhersage zu haben
– dual
20. Oktober 2012 um 1:48 Uhr
@hippietrail, Hotspot (JVM) verwendet selbst modifizierbaren Code als Norm, in Java7 gibt es INVOKE_DYNAMIC, das bei korrekter Implementierung das tut, was das OP will, und es ist Teil der Sprache (nicht wirklich Java, sondern Java-Bytecode). static final In Java wird es in der Regel auf konstant gesetzt und dann konstant gefaltet, der Code erfordert ein wenig zusätzliche Trickserei, aber es ist durchaus möglich, dass Code ohne Last entsteht val und Folgezweig.
– bestes
24. Oktober 2012 um 11:59 Uhr
gcc hat eine eingebaute Funktion, mit der Sie die Implementierung über die Verzweigungsvorhersage informieren können:
bool val = true;
void function1()
{
if (__builtin_expect(val, 0)) {
// do something
val = false;
}
else {
// do other stuff, val is never set to true again
}
}
Nützlich zu wissen __builtin_expect, aber es wird hier vernachlässigbare Auswirkungen haben: höchstens eine Handvoll Sprünge werden falsch vorhergesagt, bevor der Verzweigungsprädiktor “realisiert”, was passiert. Es scheint, als wäre es nützlicher, wenn es eine Tendenz zu einem der beiden Ergebnisse gibt, aber kein erkennbares regelmäßiges Muster.
– j_random_hacker
20. Oktober 2012 um 1:03 Uhr
Beste Antwort, die ich hier sehe. Keine vorzeitige Optimierung, sondern dem Compiler nur zusätzliche Informationen geben.
– dual
20. Oktober 2012 um 1:51 Uhr
@j_random_hacker: es sei denn, die Funktionsaufrufe sind weit genug voneinander entfernt, dass die Anweisung zwischen den Ausführungen aus dem Cache gelöscht wird. Beachten Sie auch, dass dieses Flag zu einer Neuordnung von Optimierungen führen kann, um die erwartete Verzweigung zu beschleunigen.
– nneonneo
20. Oktober 2012 um 5:38 Uhr
Zusätzlich zu __builtin_expect, das ich nur hinzufügen würde, wenn dies wirklich viel verwendeter Code ist und es wirklich keine andere Möglichkeit gibt, es zu lösen, ist es eine gute Praxis, den “normalen” Fall in den if- und den “seltenen” Fall zu setzen der Else-Zweig. Hilft möglicherweise nicht auf allen Zielplattformen, kann aber auf einigen einen Unterschied machen.
–Andreas
20. Oktober 2012 um 8:15 Uhr
@nneonneo: Wenn es aus dem Cache geleert wird, muss es relativ selten ausgeführt werden, was darauf hindeutet, dass es sich nicht lohnt, sich über einen zusätzlichen Zyklus unserer beiden Gedanken zu machen. Könnte sich aber dennoch für eingebettete Arbeiten lohnen.
– j_random_hacker
22. Oktober 2012 um 6:16 Uhr
Sie sollten die Änderung nur vornehmen, wenn Sie sicher sind, dass es sich tatsächlich um einen Engpass handelt. Mit Verzweigungsvorhersage, die if Aussage ist wahrscheinlich sofort, da es ein sehr vorhersehbares Muster ist.
erster Lauf anschließender Anruf anschließender Anruf
Ob das die Sache tatsächlich besser oder schlechter macht, würde ich gerne messen. Wenn der Compiler nun das Ziel des Aufrufs als unbekannt behandeln muss, könnte die Tatsache, dass das Ziel einen Zweig weniger enthält, vollständig durch die Unfähigkeit aufgewogen werden, Optimierungen über den Aufruf hinweg anzuwenden.
– Ben
20. Oktober 2012 um 10:44 Uhr
@ Ben auf jeden Fall. Ich bezweifle, dass es überhaupt einen Unterschied geben wird.
– Luchian Grigore
20. Oktober 2012 um 12:12 Uhr
Sie könnten einen Funktionszeiger verwenden, aber dann ist in jedem Fall ein indirekter Aufruf erforderlich:
Ich weiß es nicht genau, aber ich vermute, dass dies langsamer ist als der ursprüngliche Code.
– GManNickG
19. Oktober 2012 um 22:52 Uhr
Es ist so langsam wie grob ein virtueller Anruf. Es könnte tatsächlich langsamer sein, ich habe darauf hingewiesen, dass es einen indirekten Aufruf benötigt.
– Jack
19. Oktober 2012 um 23:15 Uhr
Richtig, mein Punkt ist, dass er darum bittet, den Overhead zu vermeiden, nicht mehr einzuführen *. (*Noch TBD, aber ich bin mir ziemlich sicher, dass es schlimmer ist.)
– GManNickG
19. Oktober 2012 um 23:22 Uhr
Ein indirekter Aufruf wäre auf den meisten Architekturen schlechter, er erfordert, dass das Laden der Adresse abgeschlossen ist, um die nächsten Befehle abzurufen. Im Originalfall wird die Verzweigungsvorhersage zu 99,9 % erfolgreich sein, also kostet es nur die Last des val
– bestes
24. Oktober 2012 um 12:08 Uhr
Eine mögliche Methode besteht darin, zwei verschiedene Versionen der Funktion zu kompilieren (dies kann aus einer einzigen Funktion in der Quelle mit Vorlagen erfolgen) und einen Funktionszeiger oder ein Objekt zu verwenden, um zur Laufzeit zu entscheiden. Der Zeiger-Overhead wird jedoch wahrscheinlich alle potenziellen Gewinne überwiegen, es sei denn, Ihre Funktion ist wirklich teuer.
Sie könnten eine verwenden static Mitgliedsvariable anstelle einer globalen Variablen..
Oder wenn der Code, den Sie zum ersten Mal ausführen, etwas für alle zukünftigen Verwendungen ändert (z. B. das Öffnen einer Datei?), können Sie diese Änderung als Prüfung verwenden, um festzustellen, ob der Code ausgeführt werden soll oder nicht (d. h. prüfen, ob die Datei ist geöffnet). Dies würde Ihnen die zusätzliche Variable ersparen. Außerdem kann es bei der Fehlerprüfung hilfreich sein – wenn aus irgendeinem Grund die anfängliche Änderung durch eine andere Operation unverändert bleibt (z. B. wenn sich die Datei auf einem Wechselmedium befindet, das unsachgemäß entfernt wurde), könnte Ihre Prüfung versuchen, die Änderung zu wiederholen.
Was ist der Vorteil eines statischen gegenüber einem globalen?
– Luchian Grigore
19. Oktober 2012 um 21:51 Uhr
In einem großen Projekt neigen globale Variablen dazu, ziemlich schnell durcheinander zu kommen. Ich würde sagen, es ist im Allgemeinen nur eine gute Codierungspraxis, obwohl es wahrscheinlich einen Vorteil beim Speicherzugriff gibt. (Zitieren Sie mich nicht)
– Elegant
19. Oktober 2012 um 21:56 Uhr
@LuchianGrigore Der Vorteil ist zweifach. Erstens, Cache-Lokalität. Sehen en.wikipedia.org/wiki/Locality_of_reference. Zweitens, wenn Sie statt eines globalen ein lokales statisches verwenden, kann der Compiler möglicherweise leichter ableiten, dass sich sein Wert nicht erneut ändert, und somit entsprechend optimieren.
– Nikos C.
19. Oktober 2012 um 21:56 Uhr
@Nikos: Cache-Ort mit was? Wovon wird das lokale Statik näher und das globale weiter entfernt sein?
– Steve Jessop
19. Oktober 2012 um 22:42 Uhr
@Steve Hmm, das war ein Hirnfurz meinerseits. Die var ist zunächst statisch, landet also sowieso im Datensegment oder BSS. Es kann sich keine Cache-Location bewerben. Wenn es nicht statisch wäre (macht natürlich keinen Sinn, aber trotzdem), hilft es, Cache-Fehler zu vermeiden, wenn die var in der Nähe der Stelle definiert ist, an der Sie sie verwenden.
– Nikos C.
19. Oktober 2012 um 22:58 Uhr
Alestanis
Ein Compiler kann nur das optimieren, was zur Kompilierzeit bekannt ist.
In Ihrem Fall der Wert von val ist nur zur Laufzeit bekannt, kann also nicht optimiert werden.
Das if Der Test ist sehr schnell, Sie sollten sich keine Gedanken darüber machen, ihn zu optimieren.
Was ist der Vorteil eines statischen gegenüber einem globalen?
– Luchian Grigore
19. Oktober 2012 um 21:51 Uhr
In einem großen Projekt neigen globale Variablen dazu, ziemlich schnell durcheinander zu kommen. Ich würde sagen, es ist im Allgemeinen nur eine gute Codierungspraxis, obwohl es wahrscheinlich einen Vorteil beim Speicherzugriff gibt. (Zitieren Sie mich nicht)
– Elegant
19. Oktober 2012 um 21:56 Uhr
@LuchianGrigore Der Vorteil ist zweifach. Erstens, Cache-Lokalität. Sehen en.wikipedia.org/wiki/Locality_of_reference. Zweitens, wenn Sie statt eines globalen ein lokales statisches verwenden, kann der Compiler möglicherweise leichter ableiten, dass sich sein Wert nicht erneut ändert, und somit entsprechend optimieren.
– Nikos C.
19. Oktober 2012 um 21:56 Uhr
@Nikos: Cache-Ort mit was? Wovon wird das lokale Statik näher und das globale weiter entfernt sein?
– Steve Jessop
19. Oktober 2012 um 22:42 Uhr
@Steve Hmm, das war ein Hirnfurz meinerseits. Die var ist zunächst statisch, landet also sowieso im Datensegment oder BSS. Es kann sich keine Cache-Location bewerben. Wenn es nicht statisch wäre (macht natürlich keinen Sinn, aber trotzdem), hilft es, Cache-Fehler zu vermeiden, wenn die var in der Nähe der Stelle definiert ist, an der Sie sie verwenden.
– Nikos C.
19. Oktober 2012 um 22:58 Uhr
ewigmatt
Wenn Sie den Code etwas sauberer machen möchten, können Sie die Variable lokal für die Funktion verwenden static:
Beim erstmaligen Aufrufen der Funktion firstRun wäre wahr, und es würde also bei jedem Aufruf der Funktion bestehen bleiben firstRun Variable wird die gleiche Instanz sein wie die davor (und wird jedes nachfolgende Mal falsch sein).
Dies könnte gut mit der Lösung von @ouah verwendet werden.
11864000cookie-checkC/C++-Optimierung entfernt Prüfungen, um zu sehen, ob eine Funktion bereits zuvor ausgeführt wurdeyes
Dies liegt im Bereich des selbstmodifizierenden Codes. Ich bezweifle, dass Sie es direkt in c oder c++ tun können
– Bwmat
19. Oktober 2012 um 21:50 Uhr
sicher kannst du. static local boolean teilt dem Compiler mit, dass ein bestimmtes Bit nur einmal ausgeführt werden soll.
– Muhende Ente
20. Oktober 2012 um 0:10 Uhr
Wenn dies für eine Laptop- / Desktop-CPU gilt, lautet die Antwort “Es spielt keine Rolle, da höchstens eine Handvoll Sprünge von jeder derzeit verwendeten CPU falsch vorhergesagt werden, wobei jeder ~ 1 ns verschwendet”.
– j_random_hacker
20. Oktober 2012 um 1:07 Uhr
@j_random_hacker Es gibt jedoch Mikrocontroller, ICs, mobile Geräte usw., bei denen selbst ein einziger Zyklus möglicherweise nicht schön zu verlieren ist. µCs neigen auch dazu, keine Verzweigungsvorhersage zu haben
– dual
20. Oktober 2012 um 1:48 Uhr
@hippietrail, Hotspot (JVM) verwendet selbst modifizierbaren Code als Norm, in Java7 gibt es INVOKE_DYNAMIC, das bei korrekter Implementierung das tut, was das OP will, und es ist Teil der Sprache (nicht wirklich Java, sondern Java-Bytecode).
static final
In Java wird es in der Regel auf konstant gesetzt und dann konstant gefaltet, der Code erfordert ein wenig zusätzliche Trickserei, aber es ist durchaus möglich, dass Code ohne Last entstehtval
und Folgezweig.– bestes
24. Oktober 2012 um 11:59 Uhr