ist [delete new int;] "as if" optimierbar oder "observable"?



  • Folgender einfacher Code:

    void foo()
    {
        delete new int;
    }
    

    Darf das laut "as if" wegoptimiert werden, oder nicht?
    Ist die Antwort auf die Frage abhängig davon ob man den globalen "new" operator selbst definiert?

    Wenn man "new" und "delete" nur als Wege sieht die entsprechenden Funktionen aufzurufen, dann ist der Code IMO "observable", und darf daher nicht wegoptimiert werden. (Ich gehe mal davon aus dass der Code in den "operator new"/"operator delete" Funktionen irgendwelche beobachtbaren Effekte hat, was er auf jedem mir bekannten System hat)

    Falls es so ist wie ich vermute, und der Code als "observable" einzustufen ist: gibt es da Pläne das zu ändern?

    Im Prinzip könnte, wenn es erlaubt wäre, der Compiler ja bestimmte Dinge komplett wegoptimieren. Beispielsweise sowas:

    void foo()
    {
        int* p = new int[2]; // new mit fixer grösse
    
        // p in Code verwenden der keine Exceptions werfen kann
    
        delete p;
    }
    

    Wenn wir mal ausser Acht lassen, dass der User den operator new und delete umdefinieren könnte, könnte der Compiler folgendes daraus machen:

    void foo()
    {
        //int* p = new int[2];
        int __storage_for_p[2];
        int* p = __storage_for_p;
    
        // p in Code verwenden der keine Exceptions werfen kann
    
        //delete p;
    }
    

    Ersparnis: 1x new und 1x delete aufrufen. Je nachdem was die Funktion macht, kann das mächtig viel um sein.

    Also...

    Kennt ihr Proposals in diese Richtung? Oder ist es gar bereits erlaubt?
    Gibt es absolute KO Faktoren die ich übersehen habe?
    Gibt es Compiler die sowas vielleicht bereits implementieren (als non-Standard Extension)?



  • Wir hatten kürzlich eine kleine Diskussion über die "as if"-Regel im Bezug auf wegoptimierte Kopien. Ich war da zunächst eher defensiver Ansicht und wurde auf folgende Dinge hingewiesen:

    • Optimierungen können immer angewandt werden, wenn sich die sichtbare Semantik des Programms nicht ändert.
    • Zusätzlich ist es im Falle von temporären Objekten und Funktionsrückgaben erlaubt, zum Zwecke der Optimierung sogar das Programmverhalten zu ändern.

    Prinzipiell sollte die Optimierung hier also erlaubt sein, sofern die normalen new - und delete -Operatoren aufgerufen werden. Ob sie in der Praxis tatsächlich angewandt wird, weiss ich nicht. Bei MSVC++ 2010 siehts jedenfalls nicht danach aus.

    Was ich mir höchstens vorstellen könnte, wären irgendwelche betriebssystemspezifischen Funktionen zur Heap-Verwaltung, die im Falle einer Optimierung ein inkonsistentes Verhalten aufweisen könnten, und deshalb darauf verzichtet würde.

    Und zum "p in Code verwenden der keine Exceptions werfen kann": Falls da Funktionsaufrufe vorkommen, ist eine umfassende semantische Analyse notwendig, um die Bedingung zu prüfen. Wenn dann sogar noch DLLs oder so im Spiel sind... Mit noexcept könnte sich das jedoch ändern.



  • Der eigentliche Aufruf der default-Allokations-/Deallokationsfunktion ist sicher nicht beobachtbar. Aber da man diese Funktionen zur Link-Zeit überschreiben kann (richtig? es ist eigentlich kein Überladen), weiß der Compiler davon in der Regel nichts.
    Dazu kommt, dass eine Allokation bad_alloc werfen kann oder den new-Handler aufrufen kann. Der Standard definiert aber nicht, wann genau das geschieht. Wenn jetzt der Speicher eigentlich voll ist und man ein "wegoptimierbares" new int (ob mit delete oder nicht ist IMO egal) sagt, muss dann zwingend die Fehlerbehandlung ausgelöst werden oder nicht?



  • Nexus schrieb:

    Wir hatten kürzlich eine kleine Diskussion über die "as if"-Regel im Bezug auf wegoptimierte Kopien. Ich war da zunächst eher defensiver Ansicht und wurde auf folgende Dinge hingewiesen:

    • Optimierungen können immer angewandt werden, wenn sich die sichtbare Semantik des Programms nicht ändert.
    • Zusätzlich ist es im Falle von temporären Objekten und Funktionsrückgaben erlaubt, zum Zwecke der Optimierung sogar das Programmverhalten zu ändern.

    So weit ist mir das alles klar 🙂

    Prinzipiell sollte die Optimierung hier also erlaubt sein, sofern die normalen new - und delete -Operatoren aufgerufen werden. Ob sie in der Praxis tatsächlich angewandt wird, weiss ich nicht. Bei MSVC++ 2010 siehts jedenfalls nicht danach aus.

    Ich bin mir da nicht ganz so sicher ob es erlaubt ist, aber egal.
    Ich bin mir 99% sicher dass es MSVC und GCC nicht machen 🙂

    Und zum "p in Code verwenden der keine Exceptions werfen kann": Falls da Funktionsaufrufe vorkommen, ist eine umfassende semantische Analyse notwendig, um die Bedingung zu prüfen. Wenn dann sogar noch DLLs oder so im Spiel sind... Mit noexcept könnte sich das jedoch ändern.

    Naja, so eine Optimierung würde vermutlich nur an Stellen möglich sein, wo fast alles inline erweitert wird. Oder zumindest der Code analysiert werden kann. Macht ja nix, muss ja nicht überall gehen.

    Richtig viel würde es auch nur in Kombination mit etwas bringen, was dem Compiler mitteilt an welchen Stellen häufig ausreichend kleine Allokationen zu erwarten sind, denn wenn es nur mit konstanter Grösse funktioniert bleibt vermutlich nicht mehr viel übrig. Entweder müsste der Programmierer mit Hilfe von diversen non-Standard Attributen z.B. explizit sagen können "das ist ein new das häufig unter N Byte liegt", oder der Compiler müsste es die Profile-Guided-Optimization Daten mitbekommen.



  • Bashar schrieb:

    ein "wegoptimierbares" new int (ob mit delete oder nicht ist IMO egal)

    Ist IMO egal, so lange der Compiler sicher weiss, dass die Adresse nirgends mehr bekannt sein kann, nachdem der entsprechende Scope verlassen wurde. (Der Scope den der Compiler sich für die Optimierung ausgesucht hat)

    Beispielsweise hier...

    void Foo()
    {
        int* p = new int;
        SomeFunction(&p);
        delete p; // oder auch nicht
    }
    

    ...wäre es nicht egal, da SomeFunction sich den Zeiger ja merken könnte.

    Ganz böser weise könnte SomeFunction auch sagen "wenn ich mir den Zeiger merke, dann schmeiss ich ne Exception, wodurch das delete p nicht ausgeführt wird, und dann gehört der int mir. Und sonst werf ich halt nix, und merk mir den Zeiger auch nicht.".

    In so einem Fall müsste der Compiler dann sicher wissen dass SomeFunction entweder nie Exceptions wirft, und daher das delete sowieso immer läuft, ODER dass SomeFunction sich den Zeiger nicht merkt (und auch keiner Funtkion weitergibt die ihn sich merkt).

    Mit Whole-Program-Optimization oder zusätzlichen Informationen in den Object- bzw. Lib-Files könnte man da vermutlich einiges ausrichten.

    Es würden denke ich genügend Fälle übrig bleiben, wo der Compiler 100% sicher sein kann, dass er maximal die Folgen eines Memory-Leak "wegoptimiert" hat, aber sonst nix schief gehen kann.



  • hustbaer schrieb:

    Es würden denke ich genügend Fälle übrig bleiben, wo der Compiler 100% sicher sein kann, dass er maximal die Folgen eines Memory-Leak "wegoptimiert" hat, aber sonst nix schief gehen kann.

    Ehrlich gesagt sehe ich nicht, dass es "genug" Fälle gibt, so dass eine Implementierung für so eine Optimierung gerechtfertigt wäre. Deine Beispiele sind ziemlich künstlich und ich hab sowas in der Praxis noch nie gesehen. Prinzipiell sind es schöne Optimierungen, aber imho real kein Stück relevant.
    Mit der tiefen Analyse steckt auch viel Aufwand drin, dazu ist es zu riskant, evtl will der Programmierer mit delete new int; ja auch das Memory-Werk in Schwung bringen, testen oder was weiß ich. Aus Versehen tippt man das jedenfalls nicht. In deinem "storage_for_p"-Beispiel könnte man den Stack schonen wollen, sowas macht man ebenfalls nicht aus Versehen (sobald man aus den C++-Windeln raus ist).

    Da haben die Compilerbauer schon richtig entschieden, bei sowas nicht rumzupfuschen sondern den Code zu lassen, wie er ist.


Anmelden zum Antworten