Performancemythen?



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



  • hustbaer schrieb:

    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 😉

    Nur gibt es garantiert nicht ein einziges Projekt das 100% Fehlerfrei ist. Zumindest wenn es mehr als nur Hello World kann... Ich bin mir nicht ganz sicher, aber ich glaube das die letzte grobe Schätzung die ich hierzu gelesen habe besagt das sich in 100 Zeilen Code im Durchschnitt 4-10 Fehler befinden.

    cu André



  • Und meist ist einfacher Code auch schnell.

    Das ist ja wohl falsch hoch 10. Dann ist Insert-Sort schneller als Quick-Sort? Dann ist stupides interpretieren schneller als ein Hotspot-Compiler?



  • @list: Vielleicht sollte doch mal jemand, der den Standard vorliegen hat, die genauen Performance-Anforderungen der list-Methoden nachschlagen (ich habe hier nur Sekundär-Literatur und die liefert widersprüchliche Angaben). Ich kann mir zumindest drei Varianten vorstellen, wie splice() und size() zusammenarbeiten können:

    • die libstdc++-Variante: splice() verbiegt nur die Zeiger und size() zählt jedes Mal durch (ergibt O(n) für size()).
    • die MSVC-Variante: splice() aktualisiert die Größenangabe (ergibt O(n) für splice().
    • lazy evaluation: splice() markiert die Größenwerte als ungültig und size() zählt nach, wenn notwendig (ergibt O(n) im worst case für size() - im Mittel haben beide vermutlich konstante Laufzeit)

    @Performance: Ich glaube nicht, daß ein stabiles Programm automatisch schnell ist. Aber im Ernstfall muß man unterscheiden zwischen Mikro-Optimierung (dazu gehört auch ++x vs. x++), die eventuell die letzten Reserven aus dem Programm rausholt, und Makro-Optimierung (Geschichten wie lazy evaluation, over-eager evaluation oder der Unterschied zwischen BubbleSort und QuickSort), mit der man das Laufzeitverhalten des Programms in der Größenordnung verändern kann.
    (btw, die Frage der list-Implementierung dürfte auch in den Bereich Makro-Optimierung fallen).



  • Was bringt eigentlich ein Just In Time Compiler?


Anmelden zum Antworten