Was passiert genau, wenn ich einen Point mit NULL initialisiere?



  • Tachyon schrieb:

    Was denn nun?

    In Bezug auf die Verwendung eines Wrappers. Ob das Beispiel Sinn macht ist doch egal.

    Zeus schrieb:

    Das ist es doch, deine beide Beispiele haben nicht das gleiche äquivalentes Code Verhalten.

    Wo liegt der Unterschied?



  • Herrlich, Paul Müller weiß ganz genau, dass er Unsinn geredet hat, aber ist zu stolz es zuzugeben. Jetzt verstrickt er sich schon in Widersprüchen.

    Gib's einfach zu: Du hast Mist erzählt. Wahre Größe zeigt der, der seine Fehler eingesteht.



  • Paul Müller schrieb:

    Zeus schrieb:

    Das ist es doch, deine beide Beispiele haben nicht das gleiche äquivalentes Code Verhalten.

    Wo liegt der Unterschied?

    Zeus schrieb:

    Oder muss ich deutlicher werden:
    Das Verhalten deines Programm ist das Verhalten, welches du selbst geschrieben hast, und das Verhalten, dass du nutzt.



  • Zeus schrieb:

    Das Verhalten deines Programm ist das Verhalten, welches du selbst geschrieben hast, und das Verhalten, dass du nutzt.

    Ja und dieses Verhalten ist zwei Zeiger, die beide auf das selbe mit new erzeugte Objekt zeigen. Wenn ich nun alle hier vorgeschlagenen Lösungen für einen Wrapper vergleiche, so bietet mir nur der shared_ptr diese Funktionalität. Die Lösung von drakon zwar auch, diese bietet aber sonst keinen Mehrwert.

    HighLigerBiMBam schrieb:

    Es wurde ganz deutlich nach einem Beispiel gefragt, welches beweisen soll, dass ctor-/dtor-Aufrufe Performanceeinbußen generieren. Du hast bisher keins vorgelegt, dass deine These glaubhaft unterstützt.

    Schein ich zwar überlesen zu haben, aber auch dafür hab ich was gebastelt.

    int main()
    {
        int bar(42); // einfach nur ein int 
        return 0;
    }
    

    Einen Wrapper der einen Wert hält und nicht speziell für dieses Problem entworfen wurde. Sicher werd ich deswegen auch wieder gesteinigt. Der vector ist einfach nur eine Aufwendige Initialisierung, die zwar im speziellen Fall nicht benötigt wird, aber zum allgemeinen Objekt gehört. Weil der vector irgendwann, zum Beispiel für eine History, benötigt wird.
    Ja das ist ein konstruiertes Beispiel, aber es ist nicht abwegig und deswegen bitte ich meine mangelnde Kreativität zu entschuldigen.

    #include <vector>
    
    class Foo
    {
        std::vector<int> m_vec;
        int m_bar;
    public:
        Foo(int bar) : m_vec(1024), m_bar(bar) {}
    };
    
    int main()
    {
        Foo foo(42);
        return 0;
    }
    
    .file   "bar.cpp"
            .text
            .p2align 4,,15
    .globl main
            .type   main, @function
    main:
    .LFB0:
            .cfi_startproc
            pushl   %ebp
            .cfi_def_cfa_offset 8
            xorl    %eax, %eax
            movl    %esp, %ebp
            .cfi_offset 5, -8
            .cfi_def_cfa_register 5
            popl    %ebp
            .cfi_restore 5
            .cfi_def_cfa 4, 4
            ret
            .cfi_endproc
    .LFE0:
            .size   main, .-main
            .ident  "GCC: (GNU) 4.5.2"
            .section        .note.GNU-stack,"",@progbits
    
    .file   "foo.cpp"
            .text
            .p2align 4,,15
    .globl main
            .type   main, @function
    main:
    .LFB446:
            .cfi_startproc
            pushl   %ebp
            .cfi_def_cfa_offset 8
            movl    %esp, %ebp
            .cfi_offset 5, -8
            .cfi_def_cfa_register 5
            andl    $-16, %esp
            subl    $16, %esp
            movl    $4096, (%esp)
            call    _Znwj
            leal    4096(%eax), %ecx
            movl    %eax, %edx
            .p2align 4,,7
            .p2align 3
    .L2:
            movl    $0, (%edx)
            addl    $4, %edx
            cmpl    %ecx, %edx
            jne     .L2
            testl   %eax, %eax
            je      .L10
            movl    %eax, (%esp)
            call    _ZdlPv
    .L10:
            xorl    %eax, %eax
            leave
            .cfi_restore 5
            .cfi_def_cfa 4, 4
            ret
            .cfi_endproc
    .LFE446:
            .size   main, .-main
            .ident  "GCC: (GNU) 4.5.2"
            .section        .note.GNU-stack,"",@progbits
    


  • Verdammt noch mal.... du vergleicht das Verhalten von Programmen, wenn du das Assembler untersuchst, nicht das von Zeiger, oder irgendeinen Smart Pointer.



  • Da kann ich auch Crysis mit Doom vergleichen. Resultat = fail.

    Einen Wrapper der einen Wert hält und nicht speziell für dieses Problem entworfen wurde. Sicher werd ich deswegen auch wieder gesteinigt. Der vector ist einfach nur eine Aufwendige Initialisierung, die zwar im speziellen Fall nicht benötigt wird, aber zum allgemeinen Objekt gehört. Weil der vector irgendwann, zum Beispiel für eine History, benötigt wird.

    Dann implementiere DIE GLEICHE MÖGLICHKEIT auch in den obigen Code mit der selben FUNKTIONALITÄT, aber ohne Klasse!

    Edit: Ich denke der Thread führt zu nichts mehr und ist längst vom Thema abgewichen. Deshalb sollte er geschlossen werden. (Auch wenn ich ihn belustigend finde.)



  • Zeus schrieb:

    Verdammt noch mal.... du vergleicht das Verhalten von Programmen, wenn du das Assembler untersuchst, nicht das von Zeiger, oder irgendeinen Smart Pointer.

    Das Assembler hat doch mit dem Verhalten der Pointer nichts zu tun. Das soll nur zeigen das Bloat entsteht durch die Pointer.
    Bei den anderen Zeigern würde zwar kein Bloat entstehen, was aber schlicht und ergreifend daran liegt, dass sie nichts machen, was man nicht auch mit dem rohen Zeiger tun würde.



  • Oh man, der shared_ptr bietet zusätzliche Funktionalität, welche ein normaler Zeiger nicht bietet. Es ist eben nicht nur ein einfacher Wrapper wie z.B. scoped_ptr. Das diese zusätzliche Funktionalität auch zusätzlichen Code benötigt sollte ja wohl klar sein. Wenn man einen shared_ptr benutzt, will man diese Funktionalität in der Regel auch. Damit ist der entstehende zusätzliche Code auch kein “Bloat“, sondern absolut notwendig.
    Dein Vergleich entspricht einem Vergleich zwischen einem PKW und einen Krankenwagen: Wenn man einen Krankenwagen als PKW benutzt ist das ziemlich “bloatig“. In dessen normalem Abwendungsfall ist die zusätzliche Funktionalität aber lebenswichtig.

    Du bist bestimmt auch einer von den Kandidaten die meinen, rohe Arrays sind schneller als ein vector, was?



  • Dann bringen wir halt noch ein weiteres Beispiel, mal sehen welche Begründung die Herren diesmal vorzubringen haben. Wie gesagt der shared_ptr war nur schnell aus dem Hut gezaubert und die zwei Zeiger damit der shared_ptr ansatzweise eine Daseinsberechtigung hatte.

    char * vs. std::string
    


  • Paul Müller schrieb:

    Dann bringen wir halt noch ein weiteres Beispiel, mal sehen welche Begründung die Herren diesmal vorzubringen haben. Wie gesagt der shared_ptr war nur schnell aus dem Hut gezaubert und die zwei Zeiger damit der shared_ptr ansatzweise eine Daseinsberechtigung hatte.

    char * vs. std::string
    

    Tachyon schrieb:

    Du bist bestimmt auch einer von den Kandidaten die meinen, rohe Arrays sind schneller als ein vector, was?

    👍



  • Du verstehst den Punkt einfach nicht, Paul Müller.

    Du behauptest, Klassen würden Codebloat mit sich bringen, den man mit Low-Level Datenstrukturen (wie rohe Zeiger oder Arrays) nicht haben würde. Das stimmt aber nur dann, wenn du die zusätzliche Funktionalität, die Klassen mit sich bringen, und sei es "nur" die Sicherheit immer delete aufzurufen, nicht haben möchtest.

    Wenn du allerdings auch dafür sorgen möchtest, dass zu jeden new ein delete vorhanden ist, oder dass keine Pufferüberläufe entstehen (char* vs. std::string), dann wird der resultierende Code gleich sein, egal ob du es jetzt mit Klassen oder rein prozedural erledigst (auch wenn Exceptionsicherheit nur mit Klassen erreichbar ist). Dass dem so ist, hat SeppJ in seinem Post schön gezeigt.



  • Das ist nicht richtig. Eine Klasse bietet viel mehr Funktionalität, die ich meist gar nicht benötige. Beispielsweise Checks die unnötig sind, weil die Eigenschaften garantiert sind. Diese werde ich wohl kaum nach programmieren. Beispiel char-Array, da allokiere ich mir die Bytes die ich benötigen könnte, um ein realloc zu vermeiden. Damit ist eine bestimmte Größe garantiert und ich benötige auch kein realloc. Ein std::string bietet dir diese Möglichkeit nicht. Dafür bietet dir der std::string durch seine Flexibilität andere Garantien, die natürlich auf kosten der Laufzeit gehen. Bei einem std::string wird man damit leben können, weil bei Text meist keine mehreren GB an Daten anfallen. Bei einer Funktion die Video-Daten verarbeitet sieht es schon wieder anders aus. Da fallen so gewaltige Datenmengen an, dass selbst für den Menschen ein zusätzlicher Maschinenbefehl spürbar werden kann.
    Außerdem scheinen einige zu vergessen, das inlining nur innerhalb einer Übersetzungseinheit funktioniert/funktionieren kann. Es mag Compiler geben, die diese Beschränkung umgehen können, doch der klassische Weg erst compilieren, dann linken, lässt überhaupt keine andere Vorgehensweise zu. Und SeppJ hatte es schon angesprochen alle seine Optimierungen gehen auf Kosten der Compiletime. Wenn man sich aber so die Trends in der Programmierung ansieht geht man lieber auf Kosten der Laufzeit in Richtung Optimierung der Compiletime (zB. Pimpl-Idiom). Ich persönlich finde es auch wesentlich sauberer Klassenbeschreibung und Implementation zu trennen.


  • Mod

    Wenn deine Klassen mehr tun als du möchtest, dann designst du deine Klassen falsch. Das ist deine Schuld und hat überhaupt nichts mit Code-Bloat und all den anderen falschen Behauptungen auf sich die du in diesem Thread aufgestellt hast.



  • Tachyon schrieb:

    Du bist bestimmt auch einer von den Kandidaten die meinen, rohe Arrays sind schneller als ein vector, was?

    War zwar nicht an mich gerichtet, aber ich werde trozdem mal darauf antworten.
    Ja.

    Iterator in/decrement ist prinzipiell ein paar cyclen langsamer, da eine addy inc/dec (in form eines pointers) meistens inline sind.

    Ctor/Dtor sind auch langsamer als die reine allocation, das obligt aber der
    zusätzliche initialisierung von membern wie _myPtr etc. sowie ein paar sicherheitsmechanismen.

    Der reine (random) zugriff á la (*iterator) ist quasi gleich schnell.

    Wenn man eins zwei mal eine vector object (und den basischen iterator) selbst proggt und dabei versucht die gleiche funktionalität wie die aus der stl zu haben sieht man die performance unterschiede.

    Was ich z.B. tunlichst vermeide ist, wenn die verwendung von std::vector angebracht ist, in schleifen diesen typ neu zu erzeugen.

    while(1)
    {
      std::vector<blub> vecBlub;
    //...
    }
    
    //dann lieber
    std::vector<blub> vecBlub;
    while(1)
    {
     vecBlub.clear();
     //...
    }
    

    Gerade da kann man sehen was die Ctor/Dtor calls eigentlich an performance kosten.

    Verzichtet man zusätzlich auf einen vector und allociert , nicht reallociert, und befreit einen Speicherbereich innerhalb der Schleife, so bekommt man noch einen zusätzlichen performance schub. (<-- realloc ist langsamer, da bei vergrößerung des speicherbereiches erst geguckt wird ob genügen "zusätzlicher" speicher vorhanden ist, oder ein weiter block frei ist mit genügen speicher, dann den speicher vom alten in den neuen block kopiert.)

    std::vector aus der stl ist kein schlechtes object und zahlreich handoptimiert sowie für viele einsatzgebiete anwendbar, vlt. sogar empfehlenswert. Aber in vielen Fällen fehlverwendet, was zu Performance einbussen von mehreren 100 000-en cyclen bedeutet.

    Greetz



  • Ihr erstellt also alle eure std::strings speziell für jedes eurer Probleme immer wieder aufs neue? Interessant! Ich nutze lieber den Vorteil von C++ und verwende bestehende Klassen wieder. Und da nehme ich auch einen gewissen Overhead in kauf. Ich weiß das man mir aus dem letzten Satz wieder einen Strick drehen will. Ich habe aber nie behauptet, dass das schlecht ist, sondern nur das es da ist und manchmal ungünstig ist. Wie zum Beispiel das Beispiel mit dem shared_ptr gezeigt hat, welches aber niemand kapieren wollte. Aber auch interessant das man wieder nicht auf die Punkt eingehen will und statt dessen lieber so unscharfe Aussagen macht, wie "deine anderen falschen Behauptungen".
    Ich tue jetzt dem HighLigerBiMBam einen Gefallen und bin hier raus. Sorry aber das ist doch keine Diskussionskultur nur Argumente zuzulassen, die den eigenen Punkt unterstützen.



  • zeusosc schrieb:

    Tachyon schrieb:

    Du bist bestimmt auch einer von den Kandidaten die meinen, rohe Arrays sind schneller als ein vector, was?

    War zwar nicht an mich gerichtet, aber ich werde trozdem mal darauf antworten.
    Ja.

    Iterator in/decrement ist prinzipiell ein paar cyclen langsamer, da eine addy inc/dec (in form eines pointers) meistens inline sind.

    Hrmmm?

    zeusosc schrieb:

    Gerade da kann man sehen was die Ctor/Dtor calls eigentlich an performance kosten.

    Woran? Ich sehe es daran nicht.

    zeusosc schrieb:

    Verzichtet man zusätzlich auf einen vector und allociert , nicht reallociert, und befreit einen Speicherbereich innerhalb der Schleife, so bekommt man noch einen zusätzlichen performance schub. (<-- realloc ist langsamer, da bei vergrößerung des speicherbereiches erst geguckt wird ob genügen "zusätzlicher" speicher vorhanden ist, oder ein weiter block frei ist mit genügen speicher, dann den speicher vom alten in den neuen block kopiert.)

    Realloc ist vor allem deshalb langsamer, weil der alte Speicherbereich in den neuen kopiert werden muss.

    zeusosc schrieb:

    std::vector aus der stl ist kein schlechtes object und zahlreich handoptimiert sowie für viele einsatzgebiete anwendbar, vlt. sogar empfehlenswert. Aber in vielen Fällen fehlverwendet, was zu Performance einbussen von mehreren 100 000-en cyclen bedeutet.

    Weil man halt alles nur dafür verwenden sollte, wofür es designed wurde? Es sei denn, es geht darum fix was zusammenzuflicken...



  • Da hier vieles durcheinander gekommen ist, will ich die Diskussion noch mal zusammen fassen.

    SeppJ schrieb:

    Paul Müller schrieb:

    Objekte verschlimmern diese ganze Angelegenheit auch nur.

    Unsinn. Abstraktion kostet keine Laufzeit.

    SeppJ schrieb:

    Idealerweise programmiert man so, dass solche Probleme gar nicht erst auftreten können.

    Paul Müller schrieb:

    Das ist meist schwierig, wenn es um zeitkritische Aufgaben geht.

    Unsinn. Abstraktion kostet keine Laufzeit.

    SeppJ schrieb:

    Du hast es ja eigentlich sehr einfach, du brauchst bloß ein Gegenbeispiel zu zeigen, bei dem eine (natürlich nicht absichtlich schlecht programmierte) Klasse auch nur einen Maschinenbefehl mehr benötigt als ein vergleichbarer Code ohne Klasse. Dein Zug.

    inlining - nicht immer möglich (nur innerhalb der gleichen ÜE, Begründung steht im Text)
    loop unrolling - nicht immer möglich (max. bei inlining)
    ungenutzte Member können nicht immer weg optimiert werden, auch bei inline nicht (siehe mein Bespiel mit dem vector)

    Das sollten wohl genug Argumente sein.

    Und danke für die Unterstützung von zeusosc. Ich denke aber Tachyon wollte auf ein paar Eigenschaften eines vectors hinaus, die keine Laufzeitunterschiede kosten. Wie der Zugriff über operator[] oder der Allokierung einer festen Größe.



  • Langsam hab ich den Verdacht, dass Paul Müller nicht weiß, was Abstraktion ist, weil er nicht weiß wie man dies in C++ ausdrückt.

    btw:

    inlining - nicht immer möglich (nur innerhalb der gleichen ÜE, Begründung steht im Text)
    loop unrolling - nicht immer möglich (max. bei inlining)

    Hast du eh kein Einfluß mehr, darüber entscheidet der Compiler für sich alleine. Höchstens als Hinweis.

    ungenutzte Member können nicht immer weg optimiert werden, auch bei inline nicht (siehe mein Bespiel mit dem vector)

    Member in eine sind immer genutzt sonst würden sie nicht drin sein. Wer dies Bestreitet, versteht Kapselung auch nicht.



  • Zeus schrieb:

    Hast du eh kein Einfluß mehr, darüber entscheidet der Compiler für sich alleine. Höchstens als Hinweis.

    Ich weiß. Und dein Hinweis bekräftigt nur meine Aussage, ich hoffe das ist dir aufgefallen.

    Zeus schrieb:

    Member in eine sind immer genutzt sonst würden sie nicht drin sein. Wer dies Bestreitet, versteht Kapselung auch nicht.

    Beispiel hier std::vector. Ein vector hat ein zusätzliches Attribut seiner tatsächlichen Größe. Das braucht aber nur der vector für Optimierungszwecke, um ein rasches realloc zu vermeiden. Wenn ich gescheit programmiere, minimiere ich den Bedarf an reallocs, wo es nur geht.
    Das heißt jetzt nicht, dass ich nicht erst mal gescheiterweise selber einen vector verwenden würde.
    Was dir vielleicht wieder nicht aufgefallen ist, dass du auch mit diesem Punkt meine Argumentation unterstützt hast.

    SeppJ schrieb:

    du brauchst bloß ein Gegenbeispiel zu zeigen, bei dem eine (natürlich nicht absichtlich schlecht programmierte) Klasse auch nur einen Maschinenbefehl mehr benötigt als ein vergleichbarer Code ohne Klasse.



  • Beispiel hier std::vector. Ein vector hat ein zusätzliches Attribut seiner tatsächlichen Größe. Das braucht aber nur der vector für Optimierungszwecke, um ein rasches realloc zu vermeiden.

    So'n Quatsch. Die Größe eines Vektors ist ein Integraler Bestandteil seines Interfaces. Wie sonst sollst du über alle Elemente eine Vektors iterieren, wenn du seine Größe nicht kennst?

    Das ist bei Arrays auch nicht anders. Du musst irgendwie die Anzahl der Elemente im Array kennen, sonst kannst du nicht darüber iterieren. Bei mit new angelegten Arrays musst du die Größe explizit speichern, bei Arrays auf dem Stack geht es evtl. noch über sizeof(), solange das Array noch nicht zu einem Pointer zerfallen ist.

    Also wieder: Dieses "zusätzliche" Attribut ist gar nicht zusätzlich, da du in jedem Fall bräuchtest.


Anmelden zum Antworten