std::unique_ptr Overhead



  • Hallo

    Ich habe eine Diskussion über Memory Safety und Zero-cost Abstractions. Um meinem Argument Kraft zu verleihen, wollte ich es mit einem kleinen Beispiel und dem Compiler Explorer untermauern. Aber zu meinem eigenen Erstaunen erzeugte die Nutzung von std::unique_ptr erheblichen Overhead: https://godbolt.org/g/ziq4Du
    Weiß jemand was hier vor sich geht? Ist das alles zurückzuführen auf den Deleter-Funktor?

    LG



  • Das ist der Overhead, um für korrektes Unwinding zu sorgen, falls f etwas wirft.
    Deine C++98 Version hat dagegen ein Leck. Fairerweise müsstest du diese Version mit try/catch schreiben.

    https://godbolt.org/g/72EwVk



  • Oh, das hab ich ganz vergessen, wohl zu lange kein new/delete mehr benutzt. 🙂
    Ich hab das nun implementiert (mit einem Scope Guard, weil try/catch je nach Exception-Modell mehr Overhead nach sich zieht) und der generierte Code ist 100% identisch: https://godbolt.org/g/19A7kn

    LG

    EDIT: value-initialization



  • Das ist auch kein sehr interessanter Schluss. Dein scope_guard ist quasi ein unique_ptr ( function ist einfach der deleter).



  • Mit noexcept sind die Codes nahezu identisch. Ich verstehe allerdings diese eine Null-Initialisierung von i nicht, die in der unique_ptr-Variante auftaucht, und dann auch nur, wenn man make_unique benutzt und nicht unique_ptr<foo>(new foo).



  • new foo
    führt Defaultintialisierung von foo aus, d.h. tut nichts, wenn foo skalar oder trivial konstruierbar ist.

    new foo()
    führt Valueinitilaisierung durch, d.h. Zero-Initialisierung, gefolgt von Defaultinitialisierung

    make_unique gibt die Funktionsargumente an einen Konstruktor weiter; wenn keine Argumente dastehen, degeneriert das aber immer noch zu einem leeren Klammerpaar -> Value-Initialisierung



  • Demonstriert aber schön, dass die Behauptung, die Verwendung von Exceptions würde keinen Overhead verursachen, so lange keine geworfen werden, immer noch komplett dahergelogen ist.



  • hustbaer schrieb:

    Demonstriert aber schön, dass die Behauptung, die Verwendung von Exceptions würde keinen Overhead verursachen, so lange keine geworfen werden, immer noch komplett dahergelogen ist.

    Diese Behauptung kann immer noch richtig sein, wenn sie in einem entsprechenden Kontext steht. "Komplett dahergelogen" ist auch komplett dahergelogen: eine Übertreibung, die nicht so universell gilt, wie der reine Wortsinn unterstellt.



  • camper schrieb:

    hustbaer schrieb:

    Demonstriert aber schön, dass die Behauptung, die Verwendung von Exceptions würde keinen Overhead verursachen, so lange keine geworfen werden, immer noch komplett dahergelogen ist.

    Diese Behauptung kann immer noch richtig sein, wenn sie in einem entsprechenden Kontext steht.

    Meinst du wenn man z.B. annimmt dass es nix aufzuräumen gibt?

    camper schrieb:

    "Komplett dahergelogen" ist auch komplett dahergelogen: eine Übertreibung, die nicht so universell gilt, wie der reine Wortsinn unterstellt.

    Ich meine schon die unqualifizierte Variante der Behauptung, also ohne Einschränkung auf einen bestimmten Kontext. Und "dahergelogen" ist natürlich eine Unterstellung die meist nicht stimmen wird - da es vermutlich meist nicht wider besseres Wissen passiert. "Falsch" wäre das bessere Wort.

    ---

    Man liest das halt leider wirklich oft. So oft, dass ich schon fast angenommen hatte dass sich da in den letzten 10 Jahren was getan hätte was ich nicht mitbekommen habe. Was dann leider doch nicht so ist.

    Ich vermute dass es theoretisch möglich sein müsste Exceptions ohne Overhead zu implementieren. Nur setzt es halt aktuell kein Compiler um. Wofür es vermutlich gute Gründe gibt, aber das ist dann wieder ein anderes Thema.



  • Wobei mir nichtmal ganz klar ist was da genau abgeht. Also wieso da mehr Register - die im Code den man sieht für gar nichts verwendet werden - gepusht werden. Davon abgesehen ist der Code ja identisch. Also eigentlich eh sehr nahe an "kein Overhead".



  • hustbaer schrieb:

    Wobei mir nichtmal ganz klar ist was da genau abgeht. Also wieso da mehr Register - die im Code den man sieht für gar nichts verwendet werden - gepusht werden. Davon abgesehen ist der Code ja identisch. Also eigentlich eh sehr nahe an "kein Overhead".

    Das ist vermutlich eine Anforderung des Stackwalkers. Im Cleanup-Code wird der Inhalt von rbp verändert aber nicht restauriert. Also wird das vermutlich vom Stackwalker organisiert, damit alles richtig ist, wenn irgenwann der Exception-Handler aufgerufen wird.
    Mit -m32 zeigt sich ein ähnliches Bild: -fomit-frame-pointer ist wirkungslos, wenn in der jeweiligen Funktion etwas aufzuräumen ist.