Performancemythen?



  • Mr. N schrieb:

    Es ist nicht festgelegt.

    Oh, das ist aber häßlich. Das heißt ich kann keine vernünftigen Laufzeitgarantien geben.

    libstdc++ hat denke ich konstantes Splice und nicht-konstantes size(), und so sollte das auch sein.

    Ja, sehe ich auch so.

    Ne andere Möglichkeit, wäre einen size-Cache zu haben, der bei splice invalidiert, sonst aber geupdatet wird.

    Das würde denke ich nicht funktionieren. Wie willst Du das implementieren? Du müßtest ja dem list-Objekt bescheid sagen, wenn was rein/rausgesplicet wird (das läuft doch nur über die Iteratoren). Damit das geht müßte jedes listen-element wissen in welcher list es gerade drin ist. Um das aber aktuell zu halten müßte man die Daten beim splicen updaten... was wiederum nicht in konstanter Zeit geht.

    Ich denke auf einer Seite muß man in den sauren Apfel beißen und O(N) bezahlen.



  • Jester schrieb:

    Das würde denke ich nicht funktionieren. Wie willst Du das implementieren? Du müßtest ja dem list-Objekt bescheid sagen, wenn was rein/rausgesplicet wird (das läuft doch nur über die Iteratoren). Damit das geht müßte jedes listen-element wissen in welcher list es gerade drin ist. Um das aber aktuell zu halten müßte man die Daten beim splicen updaten... was wiederum nicht in konstanter Zeit geht.

    Was meinst du, warum man bei splice() auch die Quell-Liste mit angeben muß?



  • CStoll schrieb:

    Was meinst du, warum man bei splice() auch die Quell-Liste mit angeben muß?

    Das weiß ich nicht. (Abgesehen davon, dass das imho eine weitere Einschränkung der Nutzbarkeit von list ist). Aber inwiefern hilft das die Zielliste upzudaten?



  • Hallo

    OOP frißt ebensowenig Performance im Vergleich zu C wie ein Makroassembler
    Performance frißt im Vergleich zu Maschinencode.

    Es sei denn, man legt es darauf an.

    Wenn man jedes einzelne Zeichen als Objekt behandeln würde und einen String dann als Liste von Zeichen-Objekten, dann kostet das Performance.
    Aber deshalb hat man ja in C++ auch noch alle Low-Level-Strukturen von C
    (Arrays usw.), die man im Zweifelsfall einsetzen kann.

    Für Performace-unkritische Anwendungen wäre die bessere Wartbarkeit dank OOP
    selbst einen gewissen Performance-Verlust wert.

    Gruß



  • kleine Bemerkung schrieb:

    Wenn man jedes einzelne Zeichen als Objekt behandeln würde und einen String dann als Liste von Zeichen-Objekten, dann kostet das Performance.

    Quatsch.



  • Jester schrieb:

    CStoll schrieb:

    Was meinst du, warum man bei splice() auch die Quell-Liste mit angeben muß?

    Das weiß ich nicht. (Abgesehen davon, dass das imho eine weitere Einschränkung der Nutzbarkeit von list ist). Aber inwiefern hilft das die Zielliste upzudaten?

    Ganz einfach: Du brauchst in den Iteratoren nicht zu speichern, welcher Liste sie gehören. Stattdessen hast du beide betroffenen Listen vor Ort (die Quelle als Parameter, das Ziel steckt hinter this) und kannst direkt deren Größenwerte anpassen (oder wenn du willst, auch nur für ungültig erklären).



  • Ohje, das heißt man muß Quell und Ziel-Liste kennen... und nicht nur Iteratoren dorthin. Naja, dann ist mir zumindest klar wie die Implementierung funktioniert. Wozu man's dann allerdings noch gebrauchen kann ist mir schleierhaft.



  • Ja, du brauchst für Splice beide Listen (und Konstruktionen wie l1.splice(p,l2,l3.begin()); sind auch undefiniert). Das hat halt den Vorteil, daß du sehr schnell Elemente zwischen verschiedenen Listen umhängen kannst.

    (Z.B. ein Scheduler schiebt ständig Threads zwischen aktiv, suspended, etc. hin und her).

    PS: Ich hab mal die Implementation aus MSVC rausgesucht:

    template<...>
    class list
    {
      ...
    public:
      size_type size() const
      { return _Size; }
    
      void splice(iterator pos, _Myt& other)
      {
        if (!other.empty())
        {
          _Splice(pos, other, other.begin(), other.end());
          _Size += other._Size;
          other._Size = 0;
        }
      }
    
      void splice(iterator pos, _Myt& other, iterator src)
      {
        iterator _L = src;
        if (pos != src && pos != ++_L)
        {
          _Splice(pos, other, src, _L);
          ++_Size;
          --_X._Size;
        }
      }
    
      void splice(iterator pos, _Myt& other, iterator first, iterator last)
      {
        if (_F != _L)
        {
          if (&other != this)
          {
            difference_type _N = 0;
            _Distance(first, last, _N);
            _Size += _N;
            other._Size -= _N;
          }
          _Splice(pos, other, first, last);
        }
      }
    ...
    };
    

    (_Size merkt sich die aktuelle Größe und _Splice() verbiegt die Zeiger, um die Elemente umzuordnen)



  • CStoll schrieb:

    Ja, du brauchst für Splice beide Listen (und Konstruktionen wie l1.splice(p,l2,l3.begin()); sind auch undefiniert). Das hat halt den Vorteil, daß du sehr schnell Elemente zwischen verschiedenen Listen umhängen kannst.

    Das könnte man aber auch, wenn man nur Iteratoren hätte ohne zu wissen in welchen Listen die Dinger konkret rumhängen. Ich hätte gern ein freies splice:

    // hängt die Liste [start, end) vor pos in die Liste ein, in der sich pos befindet.
    void splice(Iterator start, Iterator end, Iterator pos);

    Ich finde das so wie's standardisiert ist eine unnötige Einschränkung, die einige Anwendungsfälle erheblich stört oder gar verhindert.

    Das Splice das Du da jetzt gepostet hat, hat aber lineare Laufzeit... es berechnet die Größe ja gleich mit.



  • Jester schrieb:

    CStoll schrieb:

    Ja, du brauchst für Splice beide Listen (und Konstruktionen wie l1.splice(p,l2,l3.begin()); sind auch undefiniert). Das hat halt den Vorteil, daß du sehr schnell Elemente zwischen verschiedenen Listen umhängen kannst.

    Das könnte man aber auch, wenn man nur Iteratoren hätte ohne zu wissen in welchen Listen die Dinger konkret rumhängen. Ich hätte gern ein freies splice:

    // hängt die Liste [start, end) vor pos in die Liste ein, in der sich pos befindet.
    void splice(Iterator start, Iterator end, Iterator pos);
    Ich finde das so wie's standardisiert ist eine unnötige Einschränkung, die einige Anwendungsfälle erheblich stört oder gar verhindert.

    Das war wohl eine Entscheidung zwischen Aufwand und einfacher Umsetzung.

    (außerdem: Ein Iterator kann normalerweise nicht in die zugeordnete Datenstruktur eingreifen, das ist eine Angelegenheit des Containers - du hast ja auch kein globales void insert(Iterator pos, Iterator::value_type val); , sondern nur entsprechende Methoden der Container)

    Das Splice das Du da jetzt gepostet hat, hat aber lineare Laufzeit... es berechnet die Größe ja gleich mit.

    Stimmt, das hat lineare Laufzeit. Aber irgendeine Einschränkung hast du immer.



  • CStoll schrieb:

    Stimmt, das hat lineare Laufzeit. Aber irgendeine Einschränkung hast du immer.

    ja und imho die falsche. Wen interessiert schon die Größe einer Liste?

    Vielleicht hab ich mich auch unklar ausgedrückt: mir ist schon klar wofür man eine liste gebrauchen kann (und in welchen Situationen). Aber ich sehe nicht, dass std::list diese Anforderungen erfüllen kann, weil es eben heftige Einschränkungen im Interface macht, die die Klasse für die spannenden Fälle nutzlos machen.



  • Jester schrieb:

    CStoll schrieb:

    Stimmt, das hat lineare Laufzeit. Aber irgendeine Einschränkung hast du immer.

    ja und imho die falsche. Wen interessiert schon die Größe einer Liste?

    dich anscheinend nicht.

    Vielleicht hab ich mich auch unklar ausgedrückt: mir ist schon klar wofür man eine liste gebrauchen kann (und in welchen Situationen). Aber ich sehe nicht, dass std::list diese Anforderungen erfüllen kann, weil es eben heftige Einschränkungen im Interface macht, die die Klasse für die spannenden Fälle nutzlos machen.

    Wenn du mit der vorhandenen std::list<> unzufrieden bist, kannst du dir gerne eine eigene Listenklasse aufbauen, die genau deine Wünsche erfüllt 😉 Und das Gute daran ist, daß du die vorhandenen STL-Algorithmen problemlos mit deiner Liste zusammenarbeiten lassen kannst.



  • CStoll schrieb:

    Wenn du mit der vorhandenen std::list<> unzufrieden bist, kannst du dir gerne eine eigene Listenklasse aufbauen, die genau deine Wünsche erfüllt 😉 Und das Gute daran ist, daß du die vorhandenen STL-Algorithmen problemlos mit deiner Liste zusammenarbeiten lassen kannst.

    ich klinke mich dann mal aus der folgenden C++-Werbesendung aus... Es ist echt unglaublich. 🙄

    Aber eine Frage sei mir noch gestattet: wieviele Listen hast Du schon als Hilfsdatenstrukturen eingesetzt?

    Und nebenbei bemerkt: die MSVC-Implementierung würde einem meiner Algorithmen von linearer zu quadratischer Laufzeit verhelfen... richtig tolles Ding.



  • Jester schrieb:

    ich klinke mich dann mal aus der folgenden C++-Werbesendung aus... Es ist echt unglaublich. 🙄

    Mach doch, was du denkst.

    Aber eine Frage sei mir noch gestattet: wieviele Listen hast Du schon als Hilfsdatenstrukturen eingesetzt?

    Bislang noch nicht viele - das liegt aber daran, daß ich meistens andere Anforderungen an meine Hilfsstrukturen habe (z.B. direkter Indexzugriff (vector) oder Sortierung (set/map)).

    Und nebenbei bemerkt: die MSVC-Implementierung würde einem meiner Algorithmen von linearer zu quadratischer Laufzeit verhelfen... richtig tolles Ding.

    Nur aus Interesse: Was ist denn das für ein Algorithmus?



  • CStoll schrieb:

    Nur aus Interesse: Was ist denn das für ein Algorithmus?

    Es geht um ein Graphen-Problem. Der Graph ist planar und hat noch einige weitere Eigenschaften. Ich zerlege den dann in immer kleinere Teile. Dadurch dass alles in Listen gespeichert ist kann ich jederzeit entlang einer Kante in zwei Teile zerlegen (und erhalte dadurch zwei unabhängige Graphen -- geht natürlich nicht immer, aber die spezielle Problemstruktur gibt das her). Durch das ständige weiter-zerlegen kann ich es mir nicht leisten für jeden Knoten festzustellen in welcher konkreten Liste er gerade drin ist.

    Wobei es da ne ganze Reihe von Listenstrukturen gibt... die Knoten sind in ner doppelt verketteten List drin, die Kanten um einen Knoten herum (im Gegenuhrzeigersinn) auch usw.



  • Hi,

    😕 😕 Wo sind denn die "Performance-Mythen" geblieben ?

    Eigentlich fand ich das Thema ganz interessant - interessanter jedenfalls als eine Diskussion über verschiedene Möglichkeiten "list" (das ich noch nie verwendet habe) zu implementieren ...

    Mein Vorschlag: Koppelt doch die Diskussion ab (kann die ForenSW bestimmt).

    @Topic: Einen "Performancemythos" hätte ich noch: "Performance ist das wichtigste an einem Programm !" (gemeint ist hiermit "niedrige Programmlaufzeiten")
    Wenn ich mal vergleiche, wie viele Klagen über die Langsamkeit und wie viele es über mangelnde Stabilität von Programmen gibt, würde ich denken, dass Zweiteres deutlich mehr Gewicht erhalten sollte.

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Eigentlich fand ich das Thema ganz interessant - interessanter jedenfalls als eine Diskussion über verschiedene Möglichkeiten "list" (das ich noch nie verwendet habe) zu implementieren ...

    tja, du vielleicht... 😉

    Mein Vorschlag: Koppelt doch die Diskussion ab (kann die ForenSW bestimmt).

    Aus meiner Sicht ist das Thema eh erledigt.

    @Topic: Einen "Performancemythos" hätte ich noch: "Performance ist das wichtigste an einem Programm !" (gemeint ist hiermit "niedrige Programmlaufzeiten")
    Wenn ich mal vergleiche, wie viele Klagen über die Langsamkeit und wie viele es über mangelnde Stabilität von Programmen gibt, würde ich denken, dass Zweiteres deutlich mehr Gewicht erhalten sollte.

    Da würde ich fast noch einen Schritt weiter gehen und behaupten, dass stabile Software meist auch schnell ist. Und zwar aus dem einfachen Grund, dass stabile Software meist so strukturiert ist, dass der Code vergleichsweise einfach ist (wäre er nicht einfach, so wäre die Software nicht so stabil). Und meist ist einfacher Code auch schnell.



  • CStoll schrieb:

    Das Splice das Du da jetzt gepostet hat, hat aber lineare Laufzeit... es berechnet die Größe ja gleich mit.

    Stimmt, das hat lineare Laufzeit. Aber irgendeine Einschränkung hast du immer.

    Ja, sorum ist es aber 1. falschrum, 2. inkonsistent mit der libstdc++, weswegen man list so verwenden sollte als wären beide Operationen O(N), wenn man portablen Code schreibt.

    Jester schrieb:

    Da würde ich fast noch einen Schritt weiter gehen und behaupten, dass stabile Software meist auch schnell ist. Und zwar aus dem einfachen Grund, dass stabile Software meist so strukturiert ist, dass der Code vergleichsweise einfach ist (wäre er nicht einfach, so wäre die Software nicht so stabil). Und meist ist einfacher Code auch schnell.

    VIM ist auch stabil, aber von einfachem oder sauberem Code würde ich da lieber nicht reden. 😃



  • Hi,

    kommt drauf an, wie man Stabilität erzeugt. Gutes Design ist eins (und erstmal das Wichtigste), aber Konsistenzchecks sind ein Anderes - und die kosten auch Zeit.

    Gruß,

    Simon2.



  • Konsistenzchecks ändern an der "Stabilität" eines Programmes meist nicht viel, bloss daran wie schlimm sich verbleibende Fehler auswirken. Wenn ein Programm 100% Fehlerfrei ist, dann braucht man keine Konsistenzchecks mehr - und dann brauchen die auch keine Zeit mehr 😉


Anmelden zum Antworten