C/C++-Optimierung entfernt Prüfungen, um zu sehen, ob eine Funktion bereits zuvor ausgeführt wurde

Lesezeit: 8 Minuten

Benutzer-Avatar
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:

 __builtin_expect 

http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html

Zum Beispiel in Ihrem Fall:

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.

Allerdings können Sie Rückrufe verwenden:

#include <iostream>
using namespace std;
typedef void (*FunPtr) (void);
FunPtr method;
void subsequentRun()
{
    std::cout << "subsequent call" << std::endl;
}
void firstRun()
{
    std::cout << "first run" << std::endl;
    method = subsequentRun;  
}
int main()
{
    method = firstRun;
    method();
    method();
    method();
}

erzeugt die Ausgabe:

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:

void (*yourFunction)(void) = &firstCall;

void firstCall() {
 ..
 yourFunction = &otherCalls;
}

void otherCalls() {
 ..
}

void main()
{
  yourFunction();
}

  • 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

Benutzer-Avatar
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

Benutzer-Avatar
ewigmatt

Wenn Sie den Code etwas sauberer machen möchten, können Sie die Variable lokal für die Funktion verwenden static:

void function() {
    static bool firstRun = true;
    if (firstRun) {
        firstRun = false;
        ...
    }
    else {
        ...
    }
}

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.

1186400cookie-checkC/C++-Optimierung entfernt Prüfungen, um zu sehen, ob eine Funktion bereits zuvor ausgeführt wurde

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy