Auto und Smartpointer Performance



  • Bin gerade dabei, den Primer durchzuarbeiten. Hier wird reichlich Gebrauch gemacht von auto zru Ableitung von Typen und Smartpointer fliegen einem auch dauernd um die Ohren.

    Kann jemand was zu der Performance dieser Paradigmen sagen?

    Ich kann mir schwer vorstellen, dass ein Programm dass immer erst implizit den Objekttypen bestimmen muss oder das Smartpointer anstatt built-in pointern verwendet schneller läuft, als konventionelle Deklarierungen



  • auto kostet nur zur compilezeit. zur laufzeit wird da nichts geschaut.

    smart-pointer kosten auch zur laufzeit. aber oft nicht mehr, als wenn man den selben effekt per hand nachstellen müßte.



  • Mein Verständnis von Smart-Pointern* ist, dass damit die Speicherverwaltung einfacher wird. Es sollen damit Speicherlöcher vermieden werden. Im Eifer des Gefechts mit Raw Pointer wird ja doch mal gerne delete oder noch schlimmer die Klammerung vergessen delete**[]**. (Und die Frage wer darf den Speicherblock wieder freigaben, erledigt sich damit auch.)

    Und wenn es dir um Performance geht, würde ich vorschlagen, dass du das einfach selbst misst. (von volkard gerade beantwortet)

    * Ich habe bisher nur mit dem osg::ref_ptr gearbeitet.



  • Smartpointer haben sogut wie keine Performancenachteile.
    Auch sind sie bei exceptions essentiell:

    void funktionDieVielleichtRumwirft()
    {
        throw 42;
    }
    void blub()
    {
        int *i = new int;
        std::unique_ptr<int> e(std::make_unique<int>());
        funktionDieVielleichtRumwirft(); //speicherleck, *i wird nicht gelöscht. *e schon.
        delete i;
    }
    


  • Wobei ich manchmal Sorgen habe, daß zu viele Smartpointer-Interfaces gemacht werden.

    void funktionDieVielleichtRumwirft(int* i)//Roher Zeiger reicht, kein Besitz im Spiel
    {
        if(*i==42)
            throw RebootCosmosException();
        out<<*i<<'\n';
        *i=0;
    }
    

    Und natürlich

    void blub()
    {
        int i=rand();//Besitz normalerweise über normales Objekt
        funktionDieVielleichtRumwirft(&i);
    }
    

    Ich verwende smart pointers selten.



  • Ich verwende sie eigentlich auch nur in kombination mit polymorphie und std::vector:

    std::vector<std::unique_ptr<IObject>>
    


  • volkard schrieb:

    Wobei ich manchmal Sorgen habe, daß zu viele Smartpointer-Interfaces gemacht werden.

    void funktionDieVielleichtRumwirft(int* i)//Roher Zeiger reicht, kein Besitz im Spiel
    {
        if(*i==42)
            throw RebootCosmosException();
        out<<*i<<'\n';
        *i=0;
    }
    

    Und natürlich

    void blub()
    {
        int i=rand();//Besitz normalerweise über normales Objekt
        funktionDieVielleichtRumwirft(&i);
    }
    

    Ich verwende smart pointers selten.

    Was möchtest du mit deinem Code Beispiel denn verdeutlichen?

    Und wieso verwendet ihr Smart Pointer selten? aus welchem Grund?


  • Mod

    Sewing schrieb:

    Und wieso verwendet ihr Smart Pointer selten? aus welchem Grund?

    Ich brauche einfach sehr, sehr selten Zeiger. Und wenn ich doch welche brauche, dann ungefähr nur in 50% aller Fälle besitzend. Die meisten Smartpointer nutze ich, wenn ich Linux-Pipehandles damit kapsele. Dafür sind die echt praktisch.



  • Meist reicht eine simple Instanz, als member einer klasse oder lokale variable. Wenn man objekte herumreicht, geschieht das durch eine referenz oder zeiger.



  • Könnt ihr Eure beiden Statements etwas ausführen? Möchte gerne lernen 🙂

    und was heisst in diesem Zusammenhang "besitzend" ?



  • int main()
    {
    	int i = 0; // Normal, so sollte es sooft wie möglich sein
    	int * p = &i; // Nicht besitzender Zeiger
    
    	std::unique_ptr<int> si = std::make_unique<int>(1); // So ist okay, aber meist unnötig
    	int * p2 = si.get(); // Nicht besitzender Zeiger
    
    	int * bi = new int(5); // SO niemals! Besitzender Zeiger
    	int * p3 = bi; // Nicht besitzender Zeiger
    	delete bi;
    }
    

    Und das besondere beim letzten Fall ist: man kann beiden Zeiger nicht ansehen wer denn nun der Besitzer ist. Das führt meist entweder dazu, dass das löschen vergessen wird, oder doppelt gelöscht wird.

    Besitzend heisst eigentlich nur, wer die Verantwortlichkeit trägt, den Zeiger zu löschen. Bei einem "normalen" Stack Objekt ist das nämlich der Compiler, der das implizit automatisch macht. Bei einem rohen besitzenden Zeiger ist das dann nämlich quasi gar nicht mehr ersichtlich und schwer zu finden. Und bei einem Smart-Pointer ist es eigentlich klar.

    Falls du mal sowas gesehen hast:

    #define SAFE_DELETE(x) if ( (x) ) { delete (x); (x) = NULL; }
    

    (Ich habs öfters in irgendwelchen "Spiele entwickeln mit C*+ (in drölfzig Tagen)"-Büchern gesehen)
    Das ist ganz fies. Damit verschleiert man eigentlich nur die Verantwortlichkeiten und obfuskiert den Code. Bis hin, dass Bugs eignebaut werden die schwierig zu finden sind.



  • Vielen Dank schonmal

    Also deine Aussage ist, so oft wie möglich stack storage verwenden?

    Und besitzend heisst also, wer sich den Speicher zuerst geholt hat?

    Was ist bei deinem Code an Zeile 9 verwerflich?


  • Mod

    Sewing schrieb:

    Was ist bei deinem Code an Zeile 9 verwerflich?

    Was passiert denn, wenn zwischen Zeile 9 und 11 irgendwo abgebogen wird? Beispielsweise mit einer Exception oder weil jemand dazwischen ein return einbaut, ohne zu ahnen, dass Zeile 9 sich darauf verlässt, dass Zeile 11 garantiert ausgeführt wird?



  • Sewing schrieb:

    Also deine Aussage ist, so oft wie möglich stack storage verwenden?

    Ich hoffe mal ich hab hier nichts vergessen:

    Der Stack ist der Regelfall.

    Der Heap findet immer dann Anwendung wenn es über den Stack nicht geht weil Speicherbereiche für den Stack zu groß werden oder die benötigte Größe zur Compilerzeit unbekannt ist, oder sich zur Laufzeit ändern(Conatiner zB). Außerdem gibt es noch die Fälle in denen Objekte ihren Scope überleben müssen weil zB eine Funktion einen Speicherbereich erzeugen soll den sie dann zurück gibt.

    Zu erwähnen wäre noch, das die Speicherplatzanforderung vom Heap nicht ganz kostenlos ist und je nach Betriebssystem auch bestimmte Regeln bei den Mindestgrößen auftreten können die den Speicher gut belasten können. Erstellt man zB haufenweise einzellne ints dann kann es passieren das ein vielfacher Wert des Speichers verbraucht wird.

    Sewing schrieb:

    Und besitzend heisst also, wer sich den Speicher zuerst geholt hat?

    Nein, der Besitzer muß nicht der Ersteller sein. Wichtig ist nur das es immer genau einen Besitzer gibt und das Eindeutig ist wer dieser Besitzer ist und das dieser Besitzer aufräumt. Es gibt durchaus Fälle in denen Speicher auf einer Seite angefordert wird, der Besitz dann aber wechselt, so das jemand anders dafür verantwortlich ist.

    Was deine Frage zu den Smartpointern angeht. Smartpointer sind ein Schutz gegen das verlieren von Speicherbereichen, außerdem machen sie den Code einfacher, da man sich das manuelle Freigeben sparen kann, zB bei Exceptions. Die einfachste Form ist nichts anderes als eine Klasse die den Zeiger enthält und ihn bei ihrer Zerstörung löscht und die im Quellcode dann als eine Art Alias auftreten kann. Je nach Komplexität und Funktionalität kann es sein das ein Smartpointer zur Laufzeit nichts kostet, da der Compiler einfache Funktionen einfach auflöst. Für Ottonormalprogramme wäre eine eventuelle Zusatzbelastung ohnehin vernachlässigbar.


  • Mod

    Xebov schrieb:

    Je nach Komplexität und Funktionalität kann es sein das ein Smartpointer zur Laufzeit nichts kostet, da der Compiler einfache Funktionen einfach auflöst. Für Ottonormalprogramme wäre eine eventuelle Zusatzbelastung ohnehin vernachlässigbar.

    Es kann übrigens sehr gut sein, dass es sogar umgekehrt ist. Die nachgebaute Funktionalität von Smartpointern kostet möglicherweise im Regelfall Laufzeit, aber der Smartpointer selber kostet nur im Ausnahmefall.



  • SmartPointer sind im Normalfall doch threadsafe (interner Counter) oder nicht?
    das kostet definitiv - somit sollte ein std::shared_ptr mehr kosten als ein std::unique_ptr

    aber sogar die Verwendung von make_shared/unique kann Performanzeinfluss haben - es kommt eben darauf an von welcher "Geschwindigkeit" wird reden (komplexe Berechnungen, bisschen Dateizugriff,...)


  • Mod

    Gast3 schrieb:

    SmartPointer sind im Normalfall doch threadsafe (interner Counter) oder nicht?

    Der Referenzzähler eines shared_ptr ist tatsächlich thread_safe und somit möglicherweise teurer als eine nicht-threadsichere Eigenimplementierung (höchstwahrscheinlich ist der Zähler aber einfach ein atomic Datentyp).

    das kostet definitiv - somit sollte ein std::shared_ptr mehr kosten als ein std::unique_ptr

    Natürlich kostet der mehr als ein unique_ptr. Ist schließlich ein ganz anderes Konzept und keine Zauberei. Aber was soll das mit Threadsicherheit zu tun haben?



  • volkard schrieb:

    auto kostet nur zur compilezeit. zur laufzeit wird da nichts geschaut.

    Auto spart sogar zur compilezeit, da nu der Typ rechts der Zuweisung ausgewertet werden muss und nicht auch noch der links davon.



  • Was sind denn die Contras gegen einen inflationären Einsatz von auto zur Typableitung?

    Doch nur die fehlende Übersicht für den Programmierer oder?



  • Bei längeren Deklarationen ist das auto schon sehr nützlich:

    std::vector<std::unique_ptr<Http::Response>>::const_iterator i = ...
    vs:
    auto i = ...
    

    Aber auch nicht bei jedem kleinen Fitzel, sonst leidet die Übersicht:

    auto i = 42;
    int i = 42;
    

Anmelden zum Antworten