if-Anweisung ausschließlich zur Compilezeit auswerten



  • Ist es möglich, eine if-Anweisung zu erstellen, die entweder zur Compilezeit oder überhaupt nicht ausgewertet wird?

    Beispiel:

    int foo(int nbits)
    {
      if (nbits==16)return 5;
      return 0;
    }
    
    int main()
    {
      int nbits=16;
      assert(foo(nbits)==5);
    
      volatile int nbits2=16;
      assert(foo(nbits2)==0);
    }
    

    Wenn der Compiler zweifelsfrei beweisen kann, dass nbits==16 gilt (z.B. wenn Inlining stattfindet), soll die Anweisung ausgeführt werden.
    Ansonsten soll die Bedingung implizit false ergeben - und zwar auch dann, wenn nbits zufällig zur Laufzeit doch 16 sein sollte.

    Dies wäre wichtig, denn damit ließen sich zahlreiche Sonderfälle besonders effizient behandeln, ohne dass die Performance des allgemeinen Falls leidet.

    Ist dies möglich, entweder mit C++17-Standardmitteln oder mithilfe von GCC-spezifischen Erweiterungen?
    Und falls nicht, ist es zumindest für einen direkten Aufruf wie foo(16) (statt foo(nbits)) möglich?



  • Du willst also zur Compilezeit eine komplette Code-Analyse über alle Variablen und Übergabeparameter fahren um derartige Sachen zu erkennen?

    Aus meiner Sicht wäre es extremer Overhead zur Compilezeit. - Falls es sowas gibt würde ich es nicht unbedingt nutzen wollen.

    Ansonsten können gängige Compiler schon ganz gut optimieren. Aus Sachen wie:

    int val = 10;
    if(val == 11)
    {
      // do stuff
    }
    else
    {
      // do other stuff
    }
    

    Wird dann gern mal im kompilierten Ergebnis (natürlich in IL und nicht C# bei .NET):

    int val = 10;
    // do other stuff
    


  • inflames2k schrieb:

    Du willst also zur Compilezeit eine komplette Code-Analyse über alle Variablen und Übergabeparameter fahren um derartige Sachen zu erkennen?

    Aus meiner Sicht wäre es extremer Overhead zur Compilezeit. - Falls es sowas gibt würde ich es nicht unbedingt nutzen wollen.

    Das käme auf die Implementation an. Diese Analysen werden vom Compiler während der Optimierungsphase ohnehin schon durchgeführt, sollten im Idealfall also überhaupt keinen Overhead verursachen. Dass unterschiedliche Optimierungsflags zu unterschiedlichen Ergebnissen führen können ist zu erwarten, aber auch hinnehmbar.

    inflames2k schrieb:

    Ansonsten können gängige Compiler schon ganz gut optimieren.

    Das stimmt. Bei angeschalteten Optimierungen würde das assert(foo(nbits)==5) komplett wegoptimiert.

    Aber: die Abfrage if (nbits==16) darf zur Laufzeit nie stattfinden! D.h. für den allgemeinen Fall (wenn nicht geinlined wird) oder wenn zwar geinlined wird, aber nbits==16 nicht bewiesen werden kann (wie im zweiten Fall wegen dem volatile), muss die Funktion sich so verhalten, als sähe sie so aus:

    int foo(int nbits)
    {
      return 0;
    }
    


  • Als Anregung möchte ich anmerken, dass es solche Vorgänge auch an anderer Stelle schon gibt (also eine verspätete Analyse, ob ein Wert zur Compilezeit bekannt ist).

    Bei folgendem Code ist die Besonderheit, dass der Shiftwert bei der PSLLDQ-Instruktion fest als immediate in die Instruktion integriert ist, d.h. der Wert von shift MUSS zur Compilezeit bekannt sein, auch wenn es sich bei shift formal um keine Konstante handelt.

    #include <xmmintrin.h>
    
    static inline __m128i foo(__m128i v,int shift)
    {
      return _mm_slli_si128(v,shift);
    }
    
    int main()
    {
      int x=5;
      auto y=foo(_mm_set1_epi16(8),x);
      char result[16];
      _mm_storeu_si128(reinterpret_cast<__m128i_u*>(result),y);
      return result[0]; //damit der Compiler nicht alles wegoptimiert
    }
    

    Trotzdem haben gcc und clang keinerlei Probleme mit solchem Code. Macht man aus dem x jedoch ein "volatile int x", kompiliert der Code wie erwartet nicht mehr.

    Allerdings weiß ich nicht, ob mir diese Erkenntnis bei meinem Problem weiterbringt.



  • MIt C++17 gibt es if constexpr , wo dem Compiler erlaubt ist, den nicht benutzten Zweig nicht zu übersetzen.



  • DocShoe schrieb:

    MIt C++17 gibt es if constexpr , wo dem Compiler erlaubt ist, den nicht benutzten Zweig nicht zu übersetzen.

    Und der Compiler macht dann eine Analyse aller Aufrufe der Funktion?

    alexa19 schrieb:

    Diese Analysen werden vom Compiler während der Optimierungsphase ohnehin schon durchgeführt, sollten im Idealfall also überhaupt keinen Overhead verursachen.

    Naja, der Compiler macht aber keine Analyse über den kompletten Programm-Code für die Optimierung sondern eigentlich nur über die einzelnen Funktionen. Das heißt er prüft nicht mit welchen Parameter-Werten eine Funktion aufgerufen wird. Wenn doch, ist mir das neu.



  • inflames2k schrieb:

    Und der Compiler macht dann eine Analyse aller Aufrufe der Funktion?

    Er versucht es. Und wenn er zu dem Schluss kommt, dass die const-ness des Ausdrucks für das vorliegende Programm erwiesen ist, dann macht er das so.



  • inflames2k schrieb:

    Naja, der Compiler macht aber keine Analyse über den kompletten Programm-Code für die Optimierung sondern eigentlich nur über die einzelnen Funktionen. Das heißt er prüft nicht mit welchen Parameter-Werten eine Funktion aufgerufen wird. Wenn doch, ist mir das neu.

    Sofern dem Compiler der Code der aufgerufenen Funktion bekannt ist, wird er das versuchen. Bei jedem Funktionsaufruf entscheidet der Compiler, ob geinlined wird oder nicht. Besonders profitabel sind dabei solche Aufrufe, bei denen der Funktion zur Compilezeit bekannte Konstanten übergeben werden, eben weil solche Abfragen wie if (nbits==16) komplett eliminiert werden können und viele weitere Optimierungen wie constant folding, store elemination, etc. durchgeführt werden können. Das funktioniert auch rekursiv - konstante Werte und bekannte Eigenschaften von Werten (z.B. x>=0 && x<100) können tief in den call graph hinein propagiert werden.

    Deswegen ist Inlining so unfassbar mächtig. Da Funktionen zu rein formalen Konstrukten werden, lässt sich mit einer gewissen Sorgfalt high-level code schreiben, der genauso schnell ist, wie handoptimierter Assemblercode es wäre.



  • DocShoe schrieb:

    MIt C++17 gibt es if constexpr , wo dem Compiler erlaubt ist, den nicht benutzten Zweig nicht zu übersetzen.

    Auf constexpr lag ein wenig meine Hoffnung, aber bei if constexpr (nbits==16) bemängelt der Compiler (zu recht): 'nbits' is not a constant expression
    Parameter dürfen offenbar auch nicht constexpr sein, sodass sich selbst eine Überladung der Funktion (constexpr und non-constexpr) sich schwierig gestalten dürfte. Aber wenn ich mir so ansehe, was manche Leute hinbekommen, dann ist mir, als ob das Problem generall lösbar sein müsste. Entweder mit constexpr oder gar einer Kombination von SFINAE und vielleicht der Zweckentfremdung einer Funktion wie _mm_slli_si128 .

    Allerdings habe ich gerade genau das gefunden, was ich brauche: __builtin_contant_p() , was immerhin für gcc und clang funktioniert.

    Das hier:

    #include <cassert>
    
    int foo(int nbits)
    {
      if  (__builtin_constant_p(nbits) && nbits==16)return 4;
      return 2;
    }
    
    int main()
    {
      int nbits=16;
      assert(foo(nbits)==4);
    
      volatile int nbits2=16;
      assert(foo(nbits2)==2);
    
      return foo(nbits)*10+foo(nbits2);
    }
    

    kompiliert zu:

    foo(int):
            mov     eax, 2    #,
            ret     
    main:
            mov     DWORD PTR [rsp-4], 16     # nbits2,
            mov     eax, DWORD PTR [rsp-4]    # nbits2.0_1, nbits2
            mov     eax, DWORD PTR [rsp-4]    # nbits2.1_2, nbits2
            mov     eax, 42   #,
            ret
    

    Das ist genau, was ich brauche - beide asserts treffen zu. 🙂


  • Mod

    DocShoe schrieb:

    MIt C++17 gibt es if constexpr , wo dem Compiler erlaubt ist, den nicht benutzten Zweig nicht zu übersetzen.

    Falsch. Der Ausdruck muss eine constant expression sein. Der nicht genommene Zweig wird garantiert nicht instantiiert. Wäre sonst ein völlig inkohärentes Feature, weil die Wohlgeformtheit des Programs von der Qualität der Analyse abhängen würde....

    Ist es möglich, eine if-Anweisung zu erstellen, die entweder zur Compilezeit oder überhaupt nicht ausgewertet wird?

    Nein. Genauso wie man nicht zwei Versionen von Funktionen anbieten kann, eine die zur Compile- und eine die zur Laufzeit aufgerufen werden soll. Weil die Semantik einer Funktion identisch bleiben soll.

    Deine Optimierung hat folglich gar keinen Zweck, oder ist unzulässig.

    alexa19 schrieb:

    D.h. für den allgemeinen Fall (wenn nicht geinlined wird) oder wenn zwar geinlined wird, aber nbits==16 nicht bewiesen werden kann (wie im zweiten Fall wegen dem volatile), muss die Funktion sich so verhalten, als sähe sie so aus:

    int foo(int nbits)
    {
      return 0;
    }
    

    Was ist denn das für ein Schwachsinn. Die Semantik deiner Funktion hängt völlig von der Raffinesse deiner Implementierung ab?


  • Mod

    Dass unterschiedliche Optimierungsflags zu unterschiedlichen Ergebnissen führen können ist zu erwarten, aber auch hinnehmbar.

    Nein. Optimierungen sollen die Semantik eben nicht verändern. Optimierungen, die Semantik zugunsten der Performance verändern, werden zumindest in C++ so gut wie nie eingesetzt, aber selbst dann handelt es sich um relativ insignifikante Fließkomma-Abweichungen. Diese Abweichungen werden nicht so krass sein wie in deinem Beispiel (außer vielleicht in Fällen von numerischer Instabilität der optimierten Algos).

    Das ist kein Qualitätsmangel, sondern ein Fehler. Viel Spaß beim Debuggen, und so.

    Edit: Ein klein wenig die Aggressivität runtergefahren. Es handelte sich um ein Missverständnis. 🤡



  • Arcoth schrieb:

    Die Semantik deiner Funktion hängt völlig von der Raffinesse deiner Implementierung ab?

    Natürlich nicht, die Beispiele hier dienten der Illustration. Der eigentliche Zweck ist es natürlich, unter gewissen Bedingungen einen schnelleren Algorithmus bzw. eine alternative Operation auszuführen. Das Endergebnis bleibt dabei immer gleich, alles andere würde keinen Sinn machen.

    Die angestrebte Lösung macht es möglich, solche Unterscheidungsfälle auch in Funktionen zu verwenden, die viele hundert Millionen Mal pro Sekunde aufgerufen werden. Für spezielle Fälle lassen sich damit erhebliche Performancegewinne erzielen und zwar ohne dass dabei alle anderen Fälle langsamer werden.


  • Mod

    alexa19 schrieb:

    Die angestrebte Lösung macht es möglich, solche Unterscheidungsfälle auch in Funktionen zu verwenden, die viele hundert Millionen Mal pro Sekunde aufgerufen werden.

    Du rufst eine Funktion Milliarden mal zur Compilezeit auf?



  • Arcoth schrieb:

    DocShoe schrieb:

    MIt C++17 gibt es if constexpr , wo dem Compiler erlaubt ist, den nicht benutzten Zweig nicht zu übersetzen.

    Falsch. Der Ausdruck muss eine constant expression sein. Der nicht genommene Zweig wird garantiert nicht instantiiert. Wäre sonst ein völlig inkohärentes Feature, weil die Wohlgeformtheit des Programs von der Qualität der Analyse abhängen würde....

    Das musste mal näher erklären. Wieso soll die Wohlgeformtheit leiden, wenn zusätzlicher Code in die Anwendung kompiliert wird, der zur Laufzeit nie ausgeführt wird?



  • Arcoth schrieb:

    Du rufst eine Funktion Milliarden mal zur Compilezeit auf?

    Aber nein. Es geht hier nicht um constexpr-Funktionen, falls du das meinst.
    Es geht um Fälle, wo man unter gewissen Bedingungen das gleiche auf eine effizientere Art tun kann, z.B.:

    `if (n==16)fast_algo_16();

    else generic_algo();`

    Wird so eine Funktion mit einer Konstante aufgerufen, ist alles ok - es wird zur Compilezeit entschieden, ob fast_algo_16() oder generic_algo() verwendet wird, es wird optimaler Code generiert.

    Wird die Funktion aber mit einem variablen/unbekannten n aufgerufen, wird eine cmp...jXX-Instruktionssequenz generiert. n ist nur mit einer gewissen Wahrscheinlichkeit 16, die Kosten dieser Instruktionen fallen aber für alle möglichen Werte von n an. Nun muss man von Fall zu Fall abwägen, ob der Geschwindigkeitsvorteil von fast_algo_16() unter Betrachtung der Wahrscheinlichkeit von 16 die Kosten der zusätzlichen Instruktionen aufwiegt.

    Hat man zu viele solcher Sonderfälle, verbringt das Programm schnell mehr Zeit mit solchen Überprüfungen als es damit verbringt, reale Arbeit zu tun. Eine gängige Variante ist daher, mehrere Funktionen bereitzustellen und die günstigste für den aktuellen Fall aufzurufen. Aber damit ist die Codequalität automatisch im Eimer. Womöglich kann es passieren, dass man den gesamten Code anpassen muss, nur weil man irgendwo eine ehemalige Compilezeit-Konstante in eine konfigurierbare Option umgewandelt hat. Das kommt für selbstrespektierende Entwickler nicht in Frage - so etwas ist die Aufgabe eines Compilers.

    Und damit wären wir bei __builtin_constant_p() . So etwas wie if (__builtin_constant_p(n) && n==16) wird immer zur Compilezeit zu true oder false ausgewertet, es wird daraus niemals auch nur eine einzige Maschineninstruktion generiert. Man kann jetzt völlig ungeniert beliebig viele Sonderfälle definieren und diese können immer nur zu einem Performancezuwachs führen, weil sich die Codeabschnitte in den ungünstigen Fällen vollständig in Luft auflösen. Sämtliche vorher notwendigen Abwägungen haben sich damit erledigt, denn die einzig mögliche Richtung ist bergauf.

    Das Resultat ist Code, der unglaublich schnell ist, wenn viel mit konstanten (und performancetechnisch günstigen) Argumenten gearbeitet wird - aber immer noch exakt so schnell wie vorher ist, wenn das nicht getan wird. Und es muss absolut nichts an der Codestruktur geändert werden, ich brauche keine zig Varianten der gleichen Funktion. Das erledigt nun der Compiler automatisch während des Inlinings. Und das ganze skaliert dynamisch mit der Güte des Optimizers und auch mit der Verfügbarkeit von __builtin_constant_p() - wenn ein Compiler das nicht kennt, kann man pauschal false annehmen und der zusätzliche Code stört den Compiler nicht weiter.
    Deswegen kann man das ganze im Lexikon unter dem Begriff "vollendete Perfektion" nachschlagen.


  • Mod

    Und es muss absolut nichts an der Codestruktur geändert werden, ich brauche keine zig Varianten der gleichen Funktion.

    ...außer den zig Varianten, die Du ohnehin definiert hast.

    alexa19 schrieb:

    Hat man zu viele solcher Sonderfälle, verbringt das Programm schnell mehr Zeit mit solchen Überprüfungen als es damit verbringt, reale Arbeit zu tun.

    Wenn du mittels __builtin_expect die Branch weights setzt, dann wird der Prozessor spekulativ den Standardpfad (not taken) ausführen. Wir verlieren damit im Normalfall quasi überhaupt nichts, weil die Branch einfach zusammen mit einer anderen Instruktion ausgeführt wird (superscalar), oder vielleicht einen Takt in der pipeline. Nichts, was in einem komplexen Algo mit mindestens Hunderten Takten eine Auswirkung haben sollte, und wenn das entsprechende Argument nicht krass unwahrscheinlich ist, hast Du am Ende hunderte Takte gespart. Wenn es also mindestens 1% der Inputs darstellt, was man bei einer solchen Optimierung ja annehmen sollte..

    Eine gängige Variante ist daher, mehrere Funktionen bereitzustellen und die günstigste für den aktuellen Fall aufzurufen. Aber damit ist die Codequalität automatisch im Eimer.

    So etwas nennt man Optimierung. Dass der Code dadurch an Schlichtheit verliert, ist "hinnehmbar", wie Du so schön sagst. Der Vorteil ist, dass wir mit unserem Verständnis der spezifischen call sites einige Fälle ausschließen können. Aber jetzt sprechen wir von Mikrooptimierung, da kann man auch gleich erstmal profilen gehen!

    Das Resultat ist Code, der unglaublich schnell ist, wenn viel mit konstanten (und performancetechnisch günstigen) Argumenten gearbeitet wird

    Schnell verglichen womit--dem gleichen Code, der kein __builtin_constant_p einsetzt? Kaum. Und abhängig von der Verteilung der Inputs ist er langsamer.

    PS: Deine Methode hat auch wenig mit Vollendung zu tun. Schau dir mal Supercompilation an. Du kannst nicht behaupten, die signifikantesten Konfigurationen zu optimieren.


  • Mod

    alexa19 schrieb:

    Aber wenn ich mir so ansehe, was manche Leute hinbekommen, dann ist mir, als ob das Problem generall lösbar sein müsste.

    Der Artikel ist m.A.n. schlecht benannt. Es geht dort nicht um konstante Ausdrücke, die irgendwie nicht konstant wären, sondern dass der Wert bestimmte konstanter Ausdrücke auch davon abhängen kann, ob eine bestimmte constexpr-Funktion bereits zuvor definiert wurde, bzw. im Fall eines constexpr-Funktionstemplates ob eine bestimmte Spezialisierung bereits instantiiert wurde. Das ist exotisch, aber letztlich eine rein lexikalische Frage, wo im Quelltext der jeweilige Ausdruck auftaucht.
    Mit deinem Problem hat das sehr wenig zu tun.

    Es existiert kein Mechanismus in Standard C++, der Funktionalität äquivalent zu __builtin_contant_p bietet.
    N3583 diskutiert die Problematik, allerdings scheint diesbzgl. in der Zwischenzeit nichts passiert zu sein.



  • Arcoth schrieb:

    Und es muss absolut nichts an der Codestruktur geändert werden, ich brauche keine zig Varianten der gleichen Funktion.

    ...außer den zig Varianten, die Du ohnehin definiert hast.

    Die gibt es nicht.

    Arcoth schrieb:

    Wenn du mittels __builtin_expect die Branch weights setzt, dann wird der Prozessor spekulativ den Standardpfad (not taken) ausführen. Wir verlieren damit im Normalfall quasi überhaupt nichts, weil die Branch einfach zusammen mit einer anderen Instruktion ausgeführt wird (superscalar), oder vielleicht einen Takt in der pipeline. Nichts, was in einem komplexen Algo mit mindestens Hunderten Takten eine Auswirkung haben sollte

    Wir reden von Funktionen, die im Schnitt 0.5-10 Takte dauern, vielleicht mal 50, sagte ich ja bereits ("hunderte Millionen Mal pro Sekunde"). Auch wenn die branch prediction immer richtig liegt, kommt es zur erheblichen Einbußen. __builtin_expect verwende ich bereits für Fälle, die sehr oft/selten zutreffen.

    Arcoth schrieb:

    So etwas nennt man Optimierung. Dass der Code dadurch an Schlichtheit verliert, ist "hinnehmbar", wie Du so schön sagst.

    Nein, schlechter und schwer zu wartender Code ist nicht hinnehmbar. Man kann Code schreiben, der wartbar ist und trotzdem in allen Situationen die maximal mögliche Performance aufweist. Darum geht es hier ja. Wenn ich irgendwo eine globale Konstante wie coordsPerMeter von 32 auf 64 ändere kann es nicht sein, dass der Code auf einmal an allen Ecken zusammenbricht. Auch dann nicht, wenn ich sie auf 50 ändere. Ich kann mir zwar ausmalen, dass die Performance etwas sinkt, weil an verschiedenen Stellen nun multipliziert und dividiert wird anstatt dass bitshifting verwendet wird, aber das ist hinnehmbar wenn ich unbedingt 50 haben möchte. Ebenso wäre diese Konsequenz hinnehmbar, wenn ich aus der Konstante eine dynamische Variable mache. Nicht hinnehmbar ist es dagegen, wenn ich aufgrund einer solchen Änderung auf einmal überall Codereparaturen anstellen muss.
    Ebenso unnötig ist es, aufgrund von Performanceoptimierungen ganze Funktionshierarchien zu klonen, nur dass man dann an einigen bedeutenden Stellen etwas verändert - vor allem, wenn es auch anders geht, wie ich bereits dargelegt habe.

    Arcoth schrieb:

    Aber jetzt sprechen wir von Mikrooptimierung, da kann man auch gleich erstmal profilen gehen!

    Darum ging es von Anfang an. Selbst ein halber gesparter Takt in einer low level-Funktion hat großen Einfluß auf die Gesamtperformance. Und ich kann dir ja den 10.000sten Profilingvorgang widmen. Vielleicht ist er auch schon vorüber, ich habe die Jahre über nicht mitgezählt.

    Arcoth schrieb:

    Schnell verglichen womit--dem gleichen Code, der kein __builtin_constant_p einsetzt? Kaum.

    Verglichen mit dem Code, der keine Sonderfälle kennt. Dass mit den Überprüfungen im konstanten Fall immer optimaler Code erzeugt wird, habe ich bereits geschrieben. Ist jetzt aber auch keine weltbewegende Erkenntnis und darum geht es nicht.

    Arcoth schrieb:

    Und abhängig von der Verteilung der Inputs ist er langsamer.

    Nein. Außer du meinst den Fall, wo die Inputs zwar zur Compilezeit unbekannt aber aufgrund der Verteilung trotz Overhead von den Sonderbehandlung profitieren würden. Diese Fälle gibt es zwar (vor allem je teurer die Funktionen werden), die können aber auch weiterhin ohne __builtin_constant_p behandelt werden. Es geht um Fälle, wo das bisher nicht profitabel war, also vor allem bei der untersten Ebene des Codes in low level-Routinen.

    camper schrieb:

    Es existiert kein Mechanismus in Standard C++, der Funktionalität äquivalent zu __builtin_contant_p bietet.
    N3583 diskutiert die Problematik, allerdings scheint diesbzgl. in der Zwischenzeit nichts passiert zu sein.

    Danke, dann muss ich mir über eine standardkonforme Lösung keine Gedanken machen. Da __builtin_constant_p einen graceful fallback ermöglicht, ist das auch überhaupt kein Problem.

    Das Thema wäre damit erfolgreich abgehakt.


  • Mod

    Arcoth schrieb:

    Wenn du mittels __builtin_expect die Branch weights setzt, dann wird der Prozessor spekulativ den Standardpfad (not taken) ausführen. Wir verlieren damit im Normalfall quasi überhaupt nichts, weil die Branch einfach zusammen mit einer anderen Instruktion ausgeführt wird (superscalar), oder vielleicht einen Takt in der pipeline. Nichts, was in einem komplexen Algo mit mindestens Hunderten Takten eine Auswirkung haben sollte

    Wir reden von Funktionen, die im Schnitt 0.5-10 Takte dauern, vielleicht mal 50, sagte ich ja bereits ("hunderte Millionen Mal pro Sekunde").

    Funktionen, die 0.5 Takte dauern? Bist Du jetzt senil geworden? ... die i7 pipeline besteht aus etwa einem Dutzend stages, und deine Funktion braucht nicht einmal instruction fetch? Darüber hinaus interessant, dass du die Naturgesetze so umbiegen kannst, dass deine Funktion vor der clock edge zurückspringt..

    Nein, schlechter und schwer zu wartender Code ist nicht hinnehmbar.

    Niemand hat behauptet, dass Code, der nicht schlicht ist, schlecht ist oder schwer zu warten. Lege mir keine Worte in den Mund.

    Man kann Code schreiben, der wartbar ist und trotzdem in allen Situationen die maximal mögliche Performance aufweist.

    [citation needed] "Maximal mögliche performance" kann nur ein ignoranter Naivling von seiner Software behaupten.

    Nicht hinnehmbar ist es dagegen, wenn ich aufgrund einer solchen Änderung auf einmal überall Codereparaturen anstellen muss.

    Doch, völlig hinnehmbar. Weil Du offenbar wesentliche, bedeutende Optimierungen vorgenommen hast, die von der Konstanz dieses Wertes abhingen. Wenn deine Optimierungen so unbedeutend sind, dass eine Anpassung ihrer Vorkommnisse dir unangemessene Arbeit bereitet, solltest du den Sinn dieser Optimierung überdenken.

    Ebenso unnötig ist es, aufgrund von Performanceoptimierungen ganze Funktionshierarchien zu klonen, nur dass man dann an einigen bedeutenden Stellen etwas verändert - vor allem, wenn es auch anders geht, wie ich bereits dargelegt habe.

    Ja, oder wenn man einfach nicht behauptet, Funktionen optimieren zu müssen, die einen halben Takt dauern.

    Arcoth schrieb:

    Aber jetzt sprechen wir von Mikrooptimierung, da kann man auch gleich erstmal profilen gehen!

    Darum ging es von Anfang an. Selbst ein halber gesparter Takt in einer low level-Funktion hat großen Einfluß auf die Gesamtperformance.

    Tatsächlich, denn nun braucht die Funktion 0 Takte!



  • Arcoth schrieb:

    Genauso wie man nicht zwei Versionen von Funktionen anbieten kann, eine die zur Compile- und eine die zur Laufzeit aufgerufen werden soll. Weil die Semantik einer Funktion identisch bleiben soll.

    Was aber IMO recht praktisch wäre. Was die Gefahr von unterschiedlichem Verhalten angeht: das selbe Problem ergibt sich doch auch bei anderen Dingen wo C++ nicht davor zurückschreckt sie zu machen/erlauben. z.B. const Overloads, T() + U() vs. U() + T() , + vs. += etc.

    Warum dann nich auch constexpr vs. non- constexpr ?


Anmelden zum Antworten