Auto und Smartpointer Performance



  • 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;
    


  • TNA schrieb:

    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.

    Uih. Klingt voll logisch. Thx.



  • Sewing schrieb:

    Was sind denn die Contras gegen einen inflationären Einsatz von auto zur Typableitung?
    Doch nur die fehlende Übersicht für den Programmierer oder?

    Ja.

    Andererseits lebe ich in anderen Sprachen ja auch damit, daß ich Variablen mit der ersten Zuweisung automatisch aushebe und daß da dann der Typ nicht steht, stört mich an diesen Sprachen am allerwenigsten.

    Vielleicht sollte man volle Pulle nur auto nehmen zum Erzeugen von Variablen. FALLS man den Typen mal deutlich machen will, dann rechts.

    auto posX=float(47.11);
    


  • SeppJ schrieb:

    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).

    SeppJ schrieb:

    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?

    Das hat viel mit Threadsicherheit zu tun, weil atomare Operationen einfach viel viel teurer sind als nicht-atomare.

    OK, es gibt einige Ausnahmen wie z.B. auf x86 atomare Loads mit Acquire-Semantik bzw. atomare Stores mit Release-Semantik. Die sind nicht teurer, wenn man mal davon absieht dass sie den Optimizer behindern. Alles was read-modify-write ist, ist aber definitiv teurer. Und für Smart-Pointer ist nur read-modify-write interessant.

    Konkret: Auf aktuellen "dicken" Intel CPUs (alle Pentium/Celeron/Core i seit Sandy Bridge) kostet eine atomare read-modify-write Operation ca. 20 Zyklen. Das ist viel besser als es noch zu Zeiten von Pentium 4 war (> 100 Zyklen), aber auch viel schlechter als nicht atomare Operationen (meist <= 1 Zyklus).

    Der ganze Cache Coherency Tanz der da aufgeführt werden muss kostet einfach Zeit.

    ps: Für viele Anwendungen ist das natürlich komplett egal. 20 Zyklen ist zwar im Vergleich mit nicht-atomaren Operationen viel, aber absolut gesehen immer noch relativ schnell. Bitte daraus also nicht ableiten dass man shared_ptr deswegen meiden sollte. Wo shared_ptr angebracht ist, nimmt man shared_ptr . Die Performance wird dabei nur in den allerwenigsten Fällen eine Rolle spielen.



  • Ich bin dafür, daß husti mir geeignete Drogen bezahlt, damit ich ihn mir chemisch ausblenden kann.


Anmelden zum Antworten