Wie heißt der Nachfolger ...



  • Original erstellt von Gregor:
    Warum?

    Warum sollte mir die Sprache aufzwingen wie ich meine Objekte anzulegen habe?

    Vielleicht ist es nur eine kleine Klasse, die sollte doch auf den Stack rauf -> kann ja sein, dass ich sie zeitkritisch verwenden will.

    Das Problem ist, dass die Sprache mir aufzwingt WIE ich meine Objekte anlegen soll... Dabei weiss ich doch wohl selber besser ob das jetzt auf den Heap (FreeStore) oder auf den Stack kommen soll. (schliesslich weiss nur ich, was ich mit diesem Objekt alles machen will)



  • Hmmm... also ich habe den Text jetzt auch mal etwas überflogen und bisher gefällt mir die Sprache größtenteils.

    Es kommt mir so vor, als ob die Sprache ein Zwischending zwischen C++ und Java ist und noch ein paar Erweiterungen hat, die keine der anderen Sprachen hat. IMHO wurden auch jeweils die richtigen Features von C++ und Java übernommen.

    Von C++ gibt es :

    • Operator Overloading
    • Templates
    • ...

    Von Java gibt es :

    • Threads als Bestandteil der Standardbibliothek
    • Eine genau festgesetzte Größe der (meisten) primitiven Datentypen
    • Garbage Collection (Übrigens : Durch den GC ist das Arbeiten mit Objekten auf dem Heap in Java schneller als in C++, allerdings ist man in C++ auf dem Stack noch schneller ;))
    • Keine "Forward Declaractions"
    • ...

    EDIT : IMHO ist aber der größte Vorteil gegenüber C++, dass eine Menge Komplexität beseitigt wurde. Bei C++ sind durch diese Komplexität ja nichtmal die Compiler standardkonform.

    [ Dieser Beitrag wurde am 24.11.2002 um 14:55 Uhr von Gregor editiert. ]



  • Original erstellt von Gregor:
    EDIT : IMHO ist aber der größte Vorteil gegenüber C++, dass eine Menge Komplexität beseitigt wurde. Bei C++ sind durch diese Komplexität ja nichtmal die Compiler standardkonform.

    Aber dadurch wurden maechtige Features entfernt...
    zB fehlt etwas wie 'unsafe' in C#

    Ich will die moeglichkeit haben, hardcore optimierungen zu betreiben, wenn ich sie einmal brauche.

    Das ist etwas was ich an den 'modernen' Sprachen nicht verstehe:
    Warum soll die Sprache klueger sein als ich?
    Warum kann ich Objekte nicht auf den Stack legen? Das kann sinnvoll sein. Warum kann ich nicht manchmal Memory selber verwalten? Das kann sinnvoll sein.

    Zu deinem Memory Argument: In C++ kannst du, wenn du gute allocatoren verwendest auch so schnell wie Java sein! Schliesslich arbeitet ein GC ja nicht mit Magie sondern nur mit einem memory-pool.



  • Original erstellt von Shade Of Mine:
    Zu deinem Memory Argument: In C++ kannst du, wenn du gute allocatoren verwendest auch so schnell wie Java sein! Schliesslich arbeitet ein GC ja nicht mit Magie sondern nur mit einem memory-pool.

    ein wenig mehr Magie solltes du ihn schon zu trauen, z.b. kann er warten bis rechenleistung frei ist um den speicher frei zu geben oder er kann ihn defragmentieren, klar kostet das insgesamt mehr rechnenleistung aber er tut es ja mit vorliebe dan wenn welche frei ist (und dan ist es wieder egal)



  • Original erstellt von Dimah:
    **
    ein wenig mehr Magie solltes du ihn schon zu trauen, z.b. kann er warten bis rechenleistung frei ist um den speicher frei zu geben oder er kann ihn defragmentieren, klar kostet das insgesamt mehr rechnenleistung aber er tut es ja mit vorliebe dan wenn welche frei ist (und dan ist es wieder egal)**

    Mir kam es vor allem darauf an, dass der Heap bei Java immer defragmentiert ist. zumindest ein großer Teil des Heaps.

    Der Heap hat bei Java 2 Bereiche. Einen Bereich mit alten Objekten und einen mit neuen Objekten. Wenn der Speicher voll ist, dann wird der GC aktiv und macht folgendes (im Bereich der neuen Objekte):

    Er geht jedes Objekt durch. Wenn es gelöscht werden kann, dann wird der Speicher freigegeben, wenn nicht, dann wird das Objekt in den Bereich der alten Objekte umkopiert. Der Speicher im Bereich der neuen Objekte wird dann natürlich freigegeben. Der Bereich der neuen Objekte ist somit jederzeit defragmentiert. Der andere Bereich wird auch nicht schnell fragmentiert, da dort nur langlebige Objekte existieren. Die werden nicht so oft gelöscht.

    PS : Kann sein, dass es doch etwas anders funktioniert. So genau kenne ich mich da auch nicht aus.

    EDIT : Was wird denn in C++ gemacht, wenn zwar überall im Speicher etwas Platz ist, aber jeweils nicht genug Platz, damit das Objekt dort reinpaßt?

    [ Dieser Beitrag wurde am 24.11.2002 um 16:33 Uhr von Gregor editiert. ]



  • Original erstellt von Gregor:
    EDIT : Was wird denn in C++ gemacht, wenn zwar überall im Speicher etwas Platz ist, aber jeweils nicht genug Platz, damit das Objekt dort reinpaßt?

    Ich bin mir nicht ganz sicher, aber ich vermute, er würde melden, dass nicht genügend freier Speicher vorhanden ist



  • Original erstellt von Etti:
    Ich bin mir nicht ganz sicher, aber ich vermute, er würde melden, dass nicht genügend freier Speicher vorhanden ist

    Vermute ich auch fast. Aber eigentlich habe ich absolut das falsche gefragt. Viel interessanter ist wohl folgendes :

    Was macht C++, wenn ein Objekt auf dem Heap angelegt wird?

    In Java wird es wohl einen Zeiger geben, der hinter das zuletzt erzeugte Objekt zeigt. Nach dem Erzeugen wird der Zeiger dann einfach um die Länge des Objekts nach hinten verschoben. Wenn der GC aktiviert wird, dann wird der Zeiger wieder auf den Anfang des Bereiches der neuen Objekte gesetzt. (So würde ich es zumindest, ohne vorher darüber nachgedacht zu haben, machen)

    Wie wird es in C++ gemacht. Wird da jeweils ein Speicherbereich gesucht, in dem das neue Objekt gespeichert werden kann?

    [ Dieser Beitrag wurde am 24.11.2002 um 17:52 Uhr von Gregor editiert. ]



  • Das sind Details, die jeder Compilerhersteller anders lösen kann. Und wenns einer gut löst, dann gönnts ihm der andere nicht und machts noch besser 😉

    Was mir an D nicht gefällt (hab aber schon länger nix mehr drüber gelesen, kann sich also gebessert haben):

    Ganz viele der mächtigen Sachen, die auf den ersten Blick vielleicht etwas kompliziert sind, fallen raus, obwohl sie im Endeffekt doch das sind, was die Sprache c++ so gut macht: Templates (habt ja gesagt, die solls jetzt doch geben), Operator-Overloading, namespaces, Mehrfachvererbung.

    Außerdem frag ich mich: Wenn ich schon ne neue Sprache mach, die viel einfacher sein soll und bei der ich (anders als bei c++) auch keinen Wert auf Rückwärtskompatibilität lege, warum verwend ich dann nicht ne anständige Syntax? Grad die Syntax isses doch, die C/C++ oft so schwierig macht, weil sie unübersichtlich und wenig intuitiv ist. Da werden laufend neue C-Dialekte geschrieben (Java, C#, D), die Sprache wird aus Gründen der Einfachkeit um ihre besten Features kastriert aber man behält auf jeden Fall immer die seltsame Syntax 😕 😕



  • Original erstellt von kartoffelsack:
    **Das sind Details, die jeder Compilerhersteller anders lösen kann. Und wenns einer gut löst, dann gönnts ihm der andere nicht und machts noch besser 😉
    **

    Es muss hier doch jemand wissen, ob das Anlegen eines Objektes in Abhängigkeit vom belegten Speicher in C++ in konstanter Zeit, in logarithmischer Zeit oder in welcher Zeit auch immer geschieht. 😕

    Weiter oben steht noch sowas wie : "Warum sollten die Sprachen klüger als ich sein. Ich bin klüger. Ich weiß, wie ich am Besten optimiere." Wie kann man das wissen, wenn man noch nichtmal sowas weiß? Die Compiler sind IMHO größtenteils klüger als die Programmierer, was das Optimieren betrifft. :p 😉



  • Es muss hier doch jemand wissen, ob das Anlegen eines Objektes in Abhängigkeit vom belegten Speicher in C++ in konstanter Zeit, in logarithmischer Zeit oder in welcher Zeit auch immer geschieht

    Nö wie soll er auch. Das dynamische Anlegen eines Objekts geschieht in C++ in zwei Schritten.
    1. Aufruf des passenden operator new der den erforderlichen Speicher besorgt
    2. Aufruf eines passenden Konstruktor.

    Wie im ersten Schritt der Speicher bereitgestellt wird ist nicht definiert und frei varierbar (op new kann schließlich global bzw. pro Klasse überladen werden.) Ein "new Objekt" kann auch ein Objekt auf dem Stack anlegen. Es ist so ziemlich alles möglich.

    Viele Implementationen rufen standardmäßig eine C *alloc Funktion auf. Diese Funktionen wiederum verwenden Betriebssystemspezifische Aufrufe und Allokatoren. Unter Linux wird meines Wissens nach hauptsächlich mit einem Slab-Allokator gearbeitet. Wer ganz genau wissen will, was da auf seiner Platform abgeht muss auf jeden Fall ganz schön tief graben oder alles selbst implementieren.

    Früher konnte man zumindest relativ gefahrlos behaupten, dass das dynamische Anlegen vieler *kleiner* Objekte ein Performancekiller war, da die Allokatoren für große Speicherblöcke optmiert waren (so wie man sie in der Regel in C braucht). Das gilt so aber nicht mehr unbedingt.

    Da es also in C++ nicht *eine* Strategie gibt, kann man auch nicht sagen ob diese in konstanter Zeit, in logarithmischer Zeit oder in welcher Zeit auch immer geschieht.



  • Original erstellt von HumeSikkins:
    **
    Da es also in C++ nicht *eine* Strategie gibt, kann man auch nicht sagen ob diese in konstanter Zeit, in logarithmischer Zeit oder in welcher Zeit auch immer geschieht.**

    Mit anderen Worten :

    Es gibt Dinge, bei denen man nicht weiß, ob sie schnell oder langsam sind. Entsprechend kann man da wegen fehlendem Wissen auch keine vernünftige Optimierung betreiben. Wenn man etwas für einen Compiler gut optimiert hat, dann ist es dadurch vielleicht auf einem anderen Compiler langsamer geworden.

    Somit spricht also nichts dagegen, an gewissen Punkten etwas Kompetenz an den Compiler abzutreten. Dadurch kriegt dieser auch weitere Möglichkeiten, zu optimieren.



  • Es gibt Dinge, bei denen man nicht weiß, ob sie schnell oder langsam sind

    Genau. Deshalb heißt es ja auch: nicht raten sondern messen!

    . Entsprechend kann man da wegen fehlendem Wissen auch keine vernünftige Optimierung betreiben.

    Dem Schluss kann ich nicht folgen. Man kann natürlich niemals eine genrell global gültige Optimierung erzielen. Jedes Verfahren hat seine Vor- und Nachteile. Das heißt aber doch nicht, dass ich nicht optimieren kann. Jede Optimierung ist aber immer nur in einem bestimmten Kontext gültig.

    Wenn man etwas für einen Compiler gut optimiert hat, dann ist es dadurch vielleicht auf einem anderen Compiler langsamer geworden.

    Natürlich. Das gilt aber doch immer. Wenn ich meinen Code für ein Pentium X optimiere, ist er auf einem Athlon wahrscheinlich langsamer.

    Somit spricht also nichts dagegen, an gewissen Punkten etwas Kompetenz an den Compiler abzutreten.

    Nö. Natürlich nicht. Das macht man ja auch in extrem vielen Situationen. Ich kümmere mich z.B. nicht um die optimale Registerbelegung.



  • Und trotzdem möchte ich vielleicht einige Objekte auf dem Stack ablegen können! Basta!



  • @gregor

    Du vergleichst hier auch Äpfel mit Birnen.

    Klar, weißt Du bei Java, wie Du am besten optimierst, weil Du weißt, wie Java den Speicher reserviert. Aber eigentlich weißt Du nicht, wie Java das macht sondern nur, wie der Java-Compiler von Sun das macht. In C gibts nun mal Compiler von vielen verschiedenen Herstellern. Und ob die Optimierung, die Du durchführst für ne VM vom MS (gibts die noch?) genausogut ist, wie sie auf der von Sun war, is ne andere Frage. Oder auch, ob eine VM-Implementierung auf System X mit Deiner Optimierung genausoviel anfangen kann wie die VM auf System Y kannst Du auch nicht sagen. Könnte ja sein, dass System Y schon besonders gute Systemfunktionen mitbringt, damit der Speicher nicht fragmentiert und die VM auf System Y diese Eigenschaft nutzt. Hochoptimierten Code wirst Du also auch in Java nur für ein System schreiben können.



  • Du kannst mal davon ausgehen, dass ich in Java garnicht in dem Maße optimiere. Ich lasse z.B. diverse Dinge von Klassen aus der Standardbibliothek erledigen, obwohl ich es schneller könnte.

    ...zurück zu anderen Sprachfeatures :

    Was habt ihr gegen Garbage-Collection?

    [ Dieser Beitrag wurde am 26.11.2002 um 11:02 Uhr von Gregor editiert. ]



  • Original erstellt von Gregor:
    Was habt ihr gegen Garbage-Collection?

    Ich mag nix für was bezahlen, was ich nicht brauche.



  • Original erstellt von volkard:
    **
    Ich mag nix für was bezahlen, was ich nicht brauche.**

    OK! ...was kostet Garbage-Collection? Performance? Ich denke, dass zumindest das Erzeugen von Objekten mit Garbage-Collection schneller sein sollte, als ohne. Der Grund steht weiter oben. Wann braucht man meistens Performance?

    • Nach dem Erzeugen eines Objekts?
    • Wenn ein Objekt nicht mehr gebraucht wird?
    • Wenn man den Garbage-Collector per Hand aufruft?

    Ich tendiere stark zur ersten Möglichkeit! 🙂



  • Hallo,
    ich hätte überhaupt nichts gegen einen GC in C++ solange selbiger optional ist.



  • Original erstellt von HumeSikkins:
    solange selbiger optional ist.

    Warum das? An welchen Stellen hat man denn ohne GC einen Vorteil? Es gibt doch optionale GCs für C++, oder? Ich habe gehört, dass die alle Müll sind. ...zumindest vom Performance-Standpunkt aus. Ist halt etwas Aufgesetztes, also kein Wunder. Die bringen nur etwas gegen Speicherlecks, was natürlich auch sehr gut ist. Viele Programme haben schließlich Speicherlecks. Warum braucht mein Opera z.B. gerade über 82 MB Speicher? Der Grund ist, dass ich längere Zeit gesurft habe, aber nicht, dass ich gerade viele innere Fenster oder so auf habe.



  • Original erstellt von Gregor:
    OK! ...was kostet Garbage-Collection? Performance? Ich denke, dass zumindest das Erzeugen von Objekten mit Garbage-Collection schneller sein sollte, als ohne.

    Wenn man Performance jagt, tut Freispeicher weh. Wenigstens das allokieren unzähliger kleiner Objekte. Oft wollen die aber aus der Anwendung heraus schon, daß man sie in Datenstrukturen wie Stack oder Queue stoft, und da ist es wieder leicht (und in den Standardklassen sogar schon drin), Seitenweise Speicher zu holen, den Speicher defragmentiert zu halten und so. Und wirklich billig.

    Das einzige mal, daß ich ernsthaft überlegt habe, nen eigenen Allokator zu bauen, war folgender Fall:
    Um in nem Simulator, wo ich Hunderte von Objekten simulieren wollte, ein wenig Parallelität zu bekommen, wollte ich die Methodenaufrufe Asynchon machen. Da ein Methodenaufruf ja dem Senden einer Nachricht entspricht, hab ich das wörtlich genommen, und echt Nachrichtenobjekte verschickt. Und die Nachrichtenobjekte haben ne Weckzeit drin, wann sie aktiviert werden sollen.
    Weil die Nachrichten in einer gemeinsamen PriorityQueue verwaltet werden sollen, aber unterschiedliche Größen haben, kann ich nur Zeiger auf die Nachrichten verwalten. Also lege ich alle Nachrichten im Freispeicher an. Ich hatte zwar nen Durchsatz von 100000 Nachrichten/Sekunde, aber naja, ich hatte auch das Gefühl, mit nem eigenen Nachrichten-Allokator könnte man da einiges mehr rausholen.

    Mal abwarten, wann sowas als nächstes vorkommt.
    Ansonsten meide ich einfach new für die kleinen Objekte und für die großen isse erstmal nicht so schlimm. Wenns irgend geht, auf den Heap mit dem Kram.

    Auf den neuen Rechnern ist es immer schlimmer mit schlechten Cache-Treffern. Ich überlege auch schon ne ganze weile dran, häufig benutze Objekte im Speicher nebeneinanderzulegen, aber da sträubt sich C++ ein wenig dagegen, was mich nicht perfekt glücklich macht.

    zur veranschaulichung das mit den vielen kleinen messages:

    class MessageBase
    {
    private:
        Time timeToWakeUp;
    public:
        MessageBase(Time ttwu)
            :timeToWakeUp(ttwu)
        {
        };
        MessageBase(const MessageBase &m);//forbidden
        MessageBase &operator=(const MessageBase &m);//forbidden
        virtual ~MessageBase(void)
        {
    //      cout<<"test\n";
        };
        Time getTime(void)
        {
            return timeToWakeUp;
        };
        virtual void shoot(void)=0;
        virtual void deactivate(void)=0;
        virtual SimObject *getTarget(void)=0;
        bool operator<(const MessageBase &m)
        {
            return timeToWakeUp<m.timeToWakeUp;
        };
    };
    template<class TARGET1,class TARGET2>
    class Message0:public MessageBase
    {
    private:
        TARGET1 *target;
        void(TARGET2::*function)();
    public:
        Message0(TARGET1 *t,Time ttwu,void(TARGET2::*f)())
            :target(t),
            MessageBase(ttwu),
            function(f)
        {
            target->_incCountOfMessages();
        };
        ~Message0(void)
        {
            deactivate();
        };
        void shoot(void)
        {
            if(target!=NULL)
                (target->*function)();
        };
        void deactivate(void)
        {
            if(target!=NULL)
            {
                target->_decCountOfMessages();
                target=NULL;
            };
        };
        SimObject *getTarget(void)
        {
            return target;
        };
    };
    template<class TARGET1,class TARGET2,class O1>
    class Message1:public MessageBase
    {
    private:
        TARGET1 *target;
        void(TARGET2::*function)(O1);
        O1 object1;
    public:
        Message1(TARGET1 *t,Time ttwu,void(TARGET2::*f)(O1),O1 o1)
            :target(t),
            MessageBase(ttwu),
            function(f),
            object1(o1)
        {
            target->_incCountOfMessages();
        };
        ~Message1(void)
        {
            deactivate();
        };
        void shoot(void)
        {
            if(target!=NULL)
                (target->*function)(object1);
        };
        void deactivate(void)
        {
            if(target!=NULL)
            {
                target->_decCountOfMessages();
                target=NULL;
            };
        };
        SimObject *getTarget(void)
        {
            return target;
        };
    };
    class Simulator
    {
    private:
        Time time;
        PtrHeap<MessageBase> messages;
        int count;
    public:
        Simulator(void)
        {
            count=0;
        };
        ~Simulator(void)
        {
            messages.flush();
        };
        Time getTime()
        {
            return time;
        };
        void addMessage(MessageBase *m)
        {
            messages.push(m);
    //      cout<<"+"<<++count<<'\n';
        };
        void deactivate(SimObject *o);
        void step(TimeDiff i);
    };
    void Simulator::deactivate(SimObject *o)
    {
        for(int i=0;i<messages.getSize();i++)
        {
            if(messages[i]->getTarget()==o)
                messages[i]->deactivate();
        };
    };
    void Simulator::step(TimeDiff i)
    {
        Time end=time+i;
        for(;;)
        {
            if(messages.isEmpty()) break;
            MessageBase *m=messages.peek();
            if(end<m->getTime()) break;
            messages.pop();
    //      cout<<"-"<<--count<<'\n';
            time=m->getTime();
    //      cout<<time<<'\r'<<flush;
            m->shoot();
            delete m;
        };
        time=end;
        cout<<time<<'\r'<<flush;
    };
    class SimObject  
    {
    private:
        Simulator *simulator;
        SimObject &operator=(const SimObject &o);
        int countOfMessages;
    protected:
        bool trace;
    public:
        SimObject(Simulator *sim)
            :simulator(sim)
            ,trace(false)
        {
            countOfMessages=0;
        };
        ~SimObject(void)
        {
            simulator->deactivate(this);
        };
        void _incCountOfMessages(void)
        {
            countOfMessages++;
            if(countOfMessages>100)
            {
                cout<<"countOfMessages=="<<countOfMessages<<"\n";
            };
        };
        void _decCountOfMessages(void)
        {
            countOfMessages--;
        };
        void setTrace(bool t)
        {
            trace=t;
        };
        template<class TARGET1,class TARGET2>
        void doLater(TARGET1 *t,TimeDiff dauer,void(TARGET2::*f)())
        {
            Message0<TARGET1,TARGET2> *m=
                new Message0<TARGET1,TARGET2>(t,simulator->getTime()+dauer,f);
            simulator->addMessage(m);
        };
        template<class TARGET1,class TARGET2,class O1>
        void doLater(TARGET1 *t,TimeDiff dauer,void(TARGET2::*f)(O1),O1 o1)
        {
            Message1<TARGET1,TARGET2,O1> *m=
                new Message1<TARGET1,TARGET2,O1>(t,simulator->getTime()+dauer,f,o1);
            simulator->addMessage(m);
        };
        template<class TARGET1,class TARGET2,class O1,class O2>
        void doLater(TARGET1 *t,TimeDiff dauer,void(TARGET2::*f)(O1,O2),O1 o1,O2 o2)
        {
            Message2<TARGET1,TARGET2,O1,O2> *m=
                new Message2<TARGET1,TARGET2,O1,O2>(t,simulator->getTime()+dauer,f,o1,o2);
            simulator->addMessage(m);
        };
        Time getTime(void)
        {
            return simulator->getTime();
        };
    };
    

Anmelden zum Antworten