Compileroptimierung - Warum sind optimiere Binarys nicht immer stabil und wie überprüft man das?



  • gcc schrieb:

    Was sollte man Fehler erkennen, die nur selten oder unregelmäßig bei einem optimierten Binary auftreten und dies bei einem nicht optimierten Binary nicht tun?

    man muß auch entscheiden, ob Änderungen im I/O-Verhalten aufgrund heftiger Optimierung akzeptabel sind oder nicht.

    Bei einem Bildschirmschoner, dessen Wartezeit sich durch eine FP-Optimierung um x Nanosekunden verlängert, würde ich es für akzeptabel halten. Bei einem Teilchendetektor vielleicht eher nicht.



  • Deswegen kann man ja normalerweise Optimierungen die zu unterschiedlichen Ergebnissen bei floating point Berechnungen führen getrennt von anderen Optimierungen konfigurieren.



  • hat schon jemand mal "Bekanntschaft" mit solchen durch die Optimierung verursachten Fehlern gemacht?
    Ich kenne das ehrlichgesagt eher aus der Theorie, in der Praxis hatte ich noch nie Probleme - würde mich daher interessieren ob jemand ein konretes Beispiel (eventuell sogar mit Code) nennen kann.



  • gdfgfdgdf schrieb:

    hat schon jemand mal "Bekanntschaft" mit solchen durch die Optimierung verursachten Fehlern gemacht?
    Ich kenne das ehrlichgesagt eher aus der Theorie, in der Praxis hatte ich noch nie Probleme - würde mich daher interessieren ob jemand ein konretes Beispiel (eventuell sogar mit Code) nennen kann.

    Wenn ich mich recht erinnere, gabs unterschiedliche mit gcc Ergebnisse bei -O3 vs -O0

    char seldom=0;//ich verlasse mich auf überlauf nach 256 inkrementierungen
    //unsigned char anders
    for(lange){
      if(++seldom==0) cout<<zwischenstand<<'\r'<<flush;
      rechne;
      rechne;
      numbercrunch;
    }
    

    Oft, quasi dauernd bei Warnungsignorierern, ist halt richtig falsch:

    int rechne(int x){
      if(x%2==0)
        return x*x;
    }
    int y=rechne(5);
    //unoptimiert y=0
    //optimiert y=quatsch
    

    //Naja, bei MS Unterschied zwischen Debug und Release:

    int* p=new int(4711);
    delete p;
    cout<<*p;//4711 oder -850Millionen oder sowas
    


  • gdfgfdgdf schrieb:

    hat schon jemand mal "Bekanntschaft" mit solchen durch die Optimierung verursachten Fehlern gemacht?
    Ich kenne das ehrlichgesagt eher aus der Theorie, in der Praxis hatte ich noch nie Probleme - würde mich daher interessieren ob jemand ein konretes Beispiel (eventuell sogar mit Code) nennen kann.

    hab viel code von PC auf PS3 portiert und viele kleine fehler gefunden (zu 98% im code, 1% gcc, 1% undefinierbar... wuerde ich sagen).

    code bugs:
    sehr viele probleme machte z.B. type casting. sowas wie

    void foo(float& v)
    {
    int foo;
    foo = *(int*)&v;
    ...
    }
    

    funzt in VC, aber gcc sieht aliasing rules anders, gerade wenn du starke optimierungen hast (nicht nur -O3, sondern speziellere flags), wird type casting nicht als barrier fuer code-reordering angesehen und mit den langen in-order pipelines war reordering fundamental wichtig fuer performance.

    gcc bugs:
    gcc hat ab und zu beim inlinen das alignment von parametern auf dem stack verhuntzt (was ich als compiler bug ansehen wuerde), was leider sehr sehr oft einfach funktioniert, aber manchmal andere variable ueberschrieb. da mit steigender optimierungsstufe die heuristik ab wann geinlined wird vergroessert wird, passierte das oefter. (code der das validiert hat wiederrum die heuristiken beeinflusst und somit nicht alle faelle gefangen).

    schwer zuweisbarer bug:
    etwas was man schwer jemandem zuschanzen kann ist sowas wie

    inv = fabsf(foo)<FLT_EPSILON?0.0f:1.0f/foo;
    ...
    

    daraus hat der compiler ein branch-free select gemacht. an sich gut, jedoch fliegt dir das mit eingeschalteten float-exceptions um die ohren.



  • volkard schrieb:

    Oft, quasi dauernd bei Warnungsignorierern, ist halt richtig falsch:

    int rechne(int x){
      if(x%2==0)
        return x*x;
    }
    int y=rechne(5);
    //unoptimiert y=0
    //optimiert y=quatsch
    

    Da kommt sowohl optimiert als auch unoptimiert Quatsch raus.
    Habe es sogar noch einmal extra richtig getestet:

    #include <stdio.h>
    
    int rechne(int x){
      if( x % 2 == 0)
        return x*x;
    }
    
    int main(){
      int y = rechne(5);
      printf("y = %i\n", y);
      return 0;
    }
    

    Mit -O0 ergibt es 1, mit -O3 ergibt es 0.
    Der If Zweig wird in rechne() bei x=5 nie betreten, der Funktion fehlt ein return, das andere return im If Zweig wird ja nie aufgerufen.
    Es ist somit gar nicht klar, was zurückgegeben werden soll, außer irgendein int.
    Bestenfalls vielleicht die Aussage, ob das Programm die Funktion fehlerfrei ausführen kann, daher dieses TRUE und FALSE, sofern der Compiler da selber was aus dem Code reinbaut.
    Wovon ich jetzt mal ausgehen muss.
    So programmiert aber trotzdem keiner. Ich zumindest nicht. Ich setze immer ein return, wenn ich in der Funktionsdeklaration einen Rückgabetyp stehen habe.



  • Tester_42 schrieb:

    Es ist somit gar nicht klar, was zurückgegeben werden soll, außer irgendein int.

    Richtig, aber irrelevant. Wichtig ist, dass etwas unterschiedliches passiert. Und wenn einer der beiden Fälle zufällig der gewünschte ist, fällt das im Test nicht auf.



  • rapso schrieb:

    schwer zuweisbarer bug:
    etwas was man schwer jemandem zuschanzen kann ist sowas wie

    inv = fabsf(foo)<FLT_EPSILON?0.0f:1.0f/foo;
    ...
    

    daraus hat der compiler ein branch-free select gemacht. an sich gut, jedoch fliegt dir das mit eingeschalteten float-exceptions um die ohren.

    Für mich ziemlich klar ein Compiler-Bug.

    Einzig wenn der Compiler berechtigt davon ausgehen könnte dass Float-Exceptions nicht aktiviert sind, dann wäre es ein Anwendungsfehler.



  • hustbaer schrieb:

    Einzig wenn der Compiler berechtigt davon ausgehen könnte dass Float-Exceptions nicht aktiviert sind, dann wäre es ein Anwendungsfehler.

    ja, solche debatten gab es.
    aus diesem grund ist es bei mir in der kategorie "unzuweisbarer fehler" 🙂

    gerade heute wieder lustiges.
    clang findet raus, dass ein code-branch einen null-ptr bekommt und auf diesen zugreifen koennte. Generiert keinen richtigen code (wieso auch, waere segmentation fault) aber findet es auch nicht noetig warning auszuspucken (esseiden ein Held hat diese unterdrueckt).
    stattdessen generiert clang silent einen UD2-instruction (quasi trap).



  • Naja immer noch besser als einfach gar keinen Code zu generieren (=Programm läuft einfach "still" drüber) 😃

    Aber im Ernst, natürlich wäre es schön bei sowas ne Warning zu bekommen.


Anmelden zum Antworten