Liste - Performance verbessern
-
@mnimda:
Ich bin mir auch nicht sicher, ob für dich eine Liste die beste Datenstruktur ist.
Wird wirklich so häufig mitten drin gelöscht/eingefügt?
-
Naja, ich will nicht nörgeln, aber eine Liste, die einen Bruchteil der Features und ein Vielfaches der Bugs einer guten std::list hat, nein, darauf hat die Welt nicht gewartet :).
Die XGenList hat keine Iteratoren ergo sehr wenige Algorithmen. Auch nicht die einfachste exception safety. Verlangt default-constructible Type. Kein custom allocator. Wenn man Iteratoren hat, dann will man auch die Laufzeitprüfungen haben wie sie die großen STLs anbieten.
Wenn man was schnelles braucht, dann dürfte die EASTL einen Blick wert sein. STLport ist auch nicht von schlechten Eltern. Dinkumware ist Hausmannskost :D. Ansonsten immer schön LTCG und PGO machen, bringt bei STL-Code locker Faktor 3 :).
-
Superlexx schrieb:
Kein custom allocator.
Will ja nicht kleinlich sein, aber das Feature (bzw. das Fehlen davon) interessiert glaube ich nicht wirklich viele Leute.
-
Du wärst erstaunt. Erwähn das mal jemandem gegenüber, der ein paar Dutzend Threads zu bedienen hat; der hätte ernsthafte Probleme damit, wenn die sich alle um den selben Heap prügeln müssten.
-
seldon schrieb:
Du wärst erstaunt. Erwähn das mal jemandem gegenüber, der ein paar Dutzend Threads zu bedienen hat; der hätte ernsthafte Probleme damit, wenn die sich alle um den selben Heap prügeln müssten.
In so einem Fall nimmt man einen Allocator (globales new/malloc Replacement) der damit gut umgehen kann.
Irgendwas ala tcmalloc, jemalloc etc.Das über std::allocator zu lösen halte ich für eine relativ schlechte Idee, das schreit geradezu nach Bugs.
std::allocator wird mit Containern mitkopiert.
Stell dir mal vor was passiert wenn Worker-Thread X eine std::list mit custom Allocator kopiert um sie in eine Queue zu stopfen, wo Thread Y sie rausnimmt und munter weiterbearbeitet.
Sieht alles ganz sicher aus, da ja kopiert wurde.
Dummerweise wurde auch der Allocator mitkopiert. Der greift dann auch munter auf die Datenstrukturen zu, die eigentlich für Thread X gedacht sind, ganz ohne Locking, weil wir ja optimieren wollten. -> BUMMWas man natürlich machen kann, ist einen Allocator zu schreiben, der einfach nur tcmalloc o.ä. kapselt. Dabei sind die Instanzen des Allocator selbst nicht Thread-affin, und können gefahrlos kopiert werden. Allerdings würde ich in dem Fall vorziehen gleich global new/malloc zu ersetzen. Ist speziell unter Windows nicht ganz trivial, aber einige fertige Allocator bringen den nötigen Code mit.
p.S.: std::list ist auch ein ganz heisses Eisen wenn's um Allocator mit State geht: nicht alle STL Implementierungen unterstützen in dem Fall splice() korrekt.
-
Superlexx schrieb:
...
Wenn man was schnelles braucht, dann dürfte die EASTL einen Blick wert.
...Hat EA die inzwischen freigegeben? Habe nirgends was gefunden, ausser einem Whitepaper.
-
otze schrieb:
Deine Liste mact genau das, was std::list macht.
Nicht ganz: Eine std::list speichert ihre Länge im allgemeinen nicht, sondern berechnet sie ad-hoc bei einem length()-Aufruf.
Der Grund ist, dass die Funktion std::list::splice(iterator p, list<T>& x, iterator f, iterator l) laut C++-Standard in Konstantzeit laufen muss. Das ist nicht zu erreichen, wenn eine std::list zu jedem Zeitpunkt ihre Länge wüsste.
Die Aufgabe der Funktion ist: Verschiebe alle Elemente im Range [f,l) aus der Liste x heraus und in die aktuelle Liste direkt vor Position p hinein. Das ist nur ein bisschen Pointer-Gebastel und ist in Konstantzeit implementierbar. Wenn aber x und *this ihre Länge speichern würden, müsste splice() zählen wie viele Elemente denn gerade verschoben wurden.
-
Christoph schrieb:
otze schrieb:
Deine Liste mact genau das, was std::list macht.
Nicht ganz: Eine std::list speichert ihre Länge im allgemeinen nicht, sondern berechnet sie ad-hoc bei einem length()-Aufruf.
Der Grund ist, dass die Funktion std::list::splice(iterator p, list<T>& x, iterator f, iterator l) laut C++-Standard in Konstantzeit laufen muss. Das ist nicht zu erreichen, wenn eine std::list zu jedem Zeitpunkt ihre Länge wüsste.
Die Aufgabe der Funktion ist: Verschiebe alle Elemente im Range [f,l) aus der Liste x heraus und in die aktuelle Liste direkt vor Position p hinein. Das ist nur ein bisschen Pointer-Gebastel und ist in Konstantzeit implementierbar. Wenn aber x und *this ihre Länge speichern würden, müsste splice() zählen wie viele Elemente denn gerade verschoben wurden.http://cplusplus.com/reference/stl/list/splice/ schrieb:
Complexity: Constant on all cases, except when x is a list object different than *this in the third function version, in which case it is linear in the range between first and last (iterator advance).
Mit 3. Variante ist dabei die Variante von dir gemeint. Wie du siehst, hat diese also (wahrscheinlich aus genau dem Grund mit der Länge) lineare Laufzeit.
-
Dinkumware hat jedenfalls constant time
sizeMethode.
-
ipsec schrieb:
Christoph schrieb:
otze schrieb:
Deine Liste mact genau das, was std::list macht.
Nicht ganz: Eine std::list speichert ihre Länge im allgemeinen nicht, sondern berechnet sie ad-hoc bei einem length()-Aufruf.
Der Grund ist, dass die Funktion std::list::splice(iterator p, list<T>& x, iterator f, iterator l) laut C++-Standard in Konstantzeit laufen muss. Das ist nicht zu erreichen, wenn eine std::list zu jedem Zeitpunkt ihre Länge wüsste.
Die Aufgabe der Funktion ist: Verschiebe alle Elemente im Range [f,l) aus der Liste x heraus und in die aktuelle Liste direkt vor Position p hinein. Das ist nur ein bisschen Pointer-Gebastel und ist in Konstantzeit implementierbar. Wenn aber x und *this ihre Länge speichern würden, müsste splice() zählen wie viele Elemente denn gerade verschoben wurden.http://cplusplus.com/reference/stl/list/splice/ schrieb:
Complexity: Constant on all cases, except when x is a list object different than *this in the third function version, in which case it is linear in the range between first and last (iterator advance).
Mit 3. Variante ist dabei die Variante von dir gemeint. Wie du siehst, hat diese also (wahrscheinlich aus genau dem Grund mit der Länge) lineare Laufzeit.
Ah, stimmt, so war das. Der Standard verlangt weder, dass std::list::splice() noch dass std::list::length() in konstanter Zeit laufen, er empfiehlt wohl nur, dass std::list::length besser in konstanter Zeit laufen sollte ("should", nicht "must").
Die Implementierungen haben also die Wahl, eine dieser beiden Funktionen in Konstantzeit zu implementieren. Die andere wird zwangsläufig langsamer laufen. Manche Implementierungen lassen std::list::length in Konstantzeit laufen, andere std::list::splice.
-
Möglich wäre ja auch ein Kompromiss, sodass sowohl
size()als auchsplice()in den meisten Fällen eine Laufzeit von O(1) haben. Nämlich, indem die gespeicherte Grösse beisplice()ungültig gemacht und bei Aufruf vonsize()neu berechnet wird. Aber das dafür notwendigeiflohnt sich wahrscheinlich nicht.