Effizienz des Speichermanagers
Hi,
ich arbeite an einer Implementierung von B+-Bäumen (oder sowas ähnlichem zumindest), in-memory. Ich habe da gestern mal mit Benchmarks angefangen, und während die Geschwindigkeit zwar in Ordnung war, ist mir aufgefallen, dass das Programm viel mehr RAM braucht als es eigentlich sollte. Theoretisch sollte es ungefähr 80MB brauchen, tatsächlich waren es hingegen über 230MB. Ein Speicherleck ist es aber nicht, es wird am Ende alles wieder ordentlich freigegeben. Der Verdacht lag auf Heap-Fragmentierung, da jeder Endknoten des Baumes in einer verketteten Liste gespeichert ist. Ich wollte das mal mit der HeapTrc-Unit überprüfen, allerdings gibt die mir pro Sekunde ungefähr eine Zeile aus und ist damit bei einer großen Anzahl Objekte leider überhaupt nicht zu gebrauchen. Ich konnte da also keine genaueren Erkenntnisse gewinnen. Dann habe ich auf StackOverflow den Tipp gefunden, den FPC-eigenen Speichermanager durch den C-Speichermanager auszutauschen, indem man die Unit "cmem" einbindet, eigentlich mit dem Ziel, mit Valgrind debuggen zu können. Allerdings hat sich bei mir das Problem überraschenderweise schon allein durch das Einbinden der Unit erübrigt: Auf einmal braucht das Programm nur noch 90MB, was nahezu perfekt ist (Das Programm braucht nach dem Start ja schon 8MB). Aber nicht nur das: Das Einfügen in den Baum dauert nun nur noch halb so lange wie vorher (~400ms statt vorher ~800ms). Warum schneidet der FPC-Speichermanager hier im Vergleich zu C so schlecht ab? Was macht C anders? |
AW: Effizienz des Speichermanagers
Wenn Du sehr viele kleine Fragmente hast, ist der Overhead vielleicht relativ gesehen zu groß. Ich würde nicht immer und überall Klassen verwenden, die guten alten Arrays tun es in vielen Fällen auch: Und die haben fast kein Overhead.
Ich habe als Knotenspeicher nämlich ein Array verwendet. Bei mir sind die Seiten konstant 8k groß, bzw. entsprechen der 'System Page Size', sodaß ich eine Klasse habe, die exakt diese Pagegröße liest und schreibt. Im Umkehrschluss ergibt sich daraus -abzüglich einiger Bytes for Bookkeeping- ca. 8160 Bytes Nutzdaten pro B+-Knoten (bei 8k System Page größe). Je nach länge des Schlüssels passen dann eben mehr oder weniger Daten in das Array. rein. Schneller geht es imho nicht. Beim laden und persistieren lese ich alles in einem Abwasch ein bzw. flushe es entsprechend performant. An dieser einen Stelle im Code habe ich dann -gut dokumentiert- etwas Adressarithmetik, die aber gekapselt ist. Ich weiss nicht, ob wir über exakt das gleiche Modell reden, fällt mir gerade ein: Ich habe B-Bäume umgesetzt, keine B+ Bäume. Trotzdem könnte das als Denkanstoß reichen, um den Speicherverbrauch zu optimieren. |
AW: Effizienz des Speichermanagers
Hast du die Exe mit Debug-Infos und/oder Stack-Frames erstellt?
|
AW: Effizienz des Speichermanagers
Zitat:
Zitat:
(Ich weiß nicht, ob das ein grundlegender Unterschied zwischen (a,b)-Bäumen und B+-Bäumen ist, oder ob unser Prof das einfach nur anders gemacht hat. Im Internet findet man zu (a,b)-Bäumen sehr wenig Informationen) Ursprünglich stand das schon länger auf meiner To-Do-Liste, diesen untersten Layer loszuwerden, allerdings ist mir dann aufgefallen, dass das so einen entscheidenden Vorteil hat: Wenn man sich einen Zeiger (Iterator) auf einen Datensatz holt, bleibt der – solange der Datensatz nicht gelöscht wird, natürlich – für immer gültig, egal was sich am Baum verändert. Man kann beliebig Knoten einfügen oder löschen, der Zeiger zeigt trotzdem noch auf den richtigen Knoten. Das ist bei der speichereffizienteren Variante nicht gegeben: Es könnte sein, dass der zugehörige Knoten in der Zwischenzeit aufgespalten oder mit einem anderen Knoten zusammengelegt wurde, wodurch er nicht mehr an der richtigen Stelle im Speicher liegt. Für meinen Anwendungsfall ist diese Persistenz sogar tatsächlich ein Vorteil. Deshalb wollte ich erst mal in Kauf nehmen, dass meine Lösung nicht die aller speichereffizienteste ist. Dass das allerdings zu so viel Fragmentierung führt, hätte ich nicht erwartet. Ich werde jetzt wahrscheinlich versuchen, mir nur für diese Records einen eigenen kleinen Speichermanager zu schreiben. @Bernhard Geyer: Ich hatte mal testweise alle Debug-Informationen rausgenommen, das hat aber nichts verändert. |
AW: Effizienz des Speichermanagers
Blöde Idee: Erzeuge 1 Mio deiner 48-Byte Blöcke in einem separaten Projekt. Wie hoch ist der Speicherverbrauch?
Jetzt erzeuge 2 Mio Blöcke. Und nun? Erzeuge 2 Mio, gib die Häfte wieder frei. Und nun? |
AW: Effizienz des Speichermanagers
Zitat:
@Namenloser: Ich nehme mal an, das Prinzip ist dir bekannt? Für deinen Anwendungsfall brauchst du nur eine direkte Freiblockliste implementieren :wink: |
AW: Effizienz des Speichermanagers
Zitat:
Delphi-Quellcode:
Und jetzt?
var
i: integer; begin // 7 MB for i := 0 to 1000000 - 1 do New(recs[i]); // 77 MB for i := 1000000 to 2000000 - 1 do New(recs[i]); // 144 MB for i := 0 to 1000000 - 1 do Dispose(recs[i]); // 83 MB end; @BUG: Freiblockliste? Nein, sagt mir nichts. |
AW: Effizienz des Speichermanagers
Zitat:
Was ein Speichermanager wie FastMM macht ist das er nicht jedes kleines Byte das freigegeben wird wieder ans OS zurück gibt sondern effektiver und schneller als das OS kleine Speicheranforderungen und Freigaben verwaltet. |
AW: Effizienz des Speichermanagers
Zitat:
Zitat:
|
AW: Effizienz des Speichermanagers
Also hier unter Linux gibt der FPC-Speichermanager auch den Speicher zurück. Der C-Manager hingegen behält ihn. Finde ich aber nicht so toll, ich finde, der Speicher sollte schon zurückgegeben werden, es laufen schließlich noch andere Programme auf dem Rechner.
|
AW: Effizienz des Speichermanagers
Zitat:
Wenn man Speicher wieder an das System zurück geben will, muss man eine Speicherwaltung implementieren, in der dieser Fall auch wirklich auftritt. Damit sind aber bestimmte Strategien nicht möglich (z.B. Next-Fit oder Worst-Fit sollten sich nicht wirklich mit dieser Anforderung vertragen) und der Verwaltungsaufwand steigt. Oft ist es auch unnötig, den Speicher zurückgeben: Ein zyklisch ablaufendes Programm wird den Speicher bald wieder benötigen. Ein Programm, was sich bald wieder beendet, gibt ihn dann frei. |
AW: Effizienz des Speichermanagers
Ich habe das mit der eigenen Speicherverwaltung jetzt so weit umgesetzt. Komme jetzt mit dem FPC-Speichermanager in Kombination mit meiner eigenen Speicherverwaltung auf ca 98 MB, also geringfügig mehr als mit dem C-Speichermanager. Geschwindigkeitsmäßig kann die Implementierung ebenfalls mithalten. Und es wird sogar der Speicher am Ende vollständig wieder freigegeben. :)
Wäre vielleicht auch was für die CodeLib, wenn ich den Code jetzt noch etwas aufräume... |
AW: Effizienz des Speichermanagers
Zitat:
Der Speicherbedarf ist linear. Ich hätte zwar mit 147 MB gerechnet (weil 7 + (77-7)*2 = 147) aber egal. Vielleicht nimmst Du mich auch nicht ernst und hast dir die Zahlen zurechtgerechnet (und dabei einen Fehler gemacht?). Interessant ist aber: Wieso kommt zum Schluß nicht 77 MB heraus? Arbeitet also der FPC-Speichermanager nicht optimal? Wäre das eine Möglichkeit, weshalb in der Tendenz der FPC-Speichermanager degeneriert? Zitat:
Zitat:
|
AW: Effizienz des Speichermanagers
Zitat:
Zitat:
B+-Bäume machen auch im Arbeitsspeicher Sinn, weil sie deutlich cachefreundlicher sind als etwa Rot-Schwarz-Bäume. Falls dich Zahlen interessieren, unser Übungsleiter hatte da mal die Performance von Rot-Schwarz-Bäumen (aus der STL) und B+-Bäumen verschiedener Größen verglichen: http://panthema.net/2007/stx-btree/speedtest/ (lustigerweise bin ich erst über einen unabhängigen Link auf StackOverflow wieder darauf gestoßen, dann fiel mir wieder ein, dass das in einer Vorlesung auch mal erwähnt wurde...) Eine Hashmap nützt mir nichts, weil ich wirklich eine sortierte Liste brauche. Eine Skiplist habe ich nicht ausprobiert, aber soweit ich weiß nähert diese sich auch letztlich propabilistisch an die Struktur eines Baumes an. Ob das jetzt nun schneller ist oder nicht, müsste man testen. Allerdings ist zu bedenken, dass eine Skiplist auch wieder eine verkettete Liste ist, das bedeutet tendenziell schlechte Cache-Effizienz und wieder Fragmentierungs-Probleme. Ich hätte so oder so beides neu implementieren müssen, und mit den Bäumen kannte ich mich besser aus ;). |
AW: Effizienz des Speichermanagers
Ich hab mir jetzt selber auch noch mal den Spaß gemacht und drei verschiedene Varianten gegeneinander getestet:
1. Rot-Schwarz-Baum aus der FCL-STL 2. alzaimars Skiplist (habe die maximale Tiefe allerdings von 8 auf 20 erhöht) 3. mein eigener B+-Baum Ergebnis eines Testlaufs (alle Zeiten in ms): B+-Baum: B+ 1000000 Inserts: 472 B+ 1000000 Locates: 303 B+ 500000 Removes: 285 B+ 500000 Reinserts: 311 Skiplist: SL 1000000 Inserts: 721 SL 1000000 Locates: 223 SL 500000 Removes: 322 SL 500000 Reinserts: 399 Rot-Schwarz-Baum: RB 1000000 Inserts: 1337 RB 1000000 Locates: 859 RB 500000 Removes: 1243 RB 500000 Reinserts: 634 Die Werte schwanken natürlich etwas von einem Durchlauf auf den anderen, aber so ungefähr passt das. Die Skiplist hat mich echt überrascht. Vor allem, wenn man bedenkt, dass die Unit nur 300 Zeilen lang ist, während der B+-Baum es auf über 1100 Zeilen bringt (wobei die Skiplist allerdings auch weniger Features hat). Wenn man an die Skiplist noch die gleiche Speicherverwaltung dranbasteln würde, von der der B+-Baum aktuell profitiert, dann könnte man vermutlich die Insert-Zeiten und den Speicherverbrauch auch noch senken – denn letzterer ist mit über 300 MB recht happig im Vergleich zu ca. 100 MB beim B+-Baum. Der Rot-Schwarz-Baum bringt es ebenfalls auf über 300 MB und gibt den Speicher zudem nicht vollständig wieder frei, außerdem ist er, wie man sieht, bei der Geschwindigkeit weit abgeschlagen. Trotz des guten Abschneidens der Skiplist kann man aber als Fazit sagen, dass B+-Bäume durchaus gut als RAM-interne Datenstruktur geeignet sind. Bei der Skiplist muss man natürlich außerdem noch berücksichtigen, dass sie propabilistisch arbeitet – bei so großen Anzahlen von Elementen kann sie damit natürlich alle ihre Vorteile ausspielen. Je kleiner die Anzahl, desto größer wäre aber die Wahrscheinlichkeit, dass ihre reale Laufzeit sich in Richtung der Worst-Case-Laufzeit verschiebt. |
AW: Effizienz des Speichermanagers
Dein B+-Tree schlägt sich offensichtlich ganz gut. Der RBTree aus der FPC-STL ist aber wohl nicht gerade der beste.
Interessant wäre noch, wenn Du das ganze noch mit dem TAVLTree aus der FCL vergleichst. In meinem Speed-Test sieht die Skiplist im Vergleich mit dem AVL-Tree oder meinem eigenen RB-Tree recht langsam aus. |
AW: Effizienz des Speichermanagers
Ich hätte jetzt nicht gedacht, das der B+ Baum in-memory so gut abschneidet. :thumb:
Die Skiplist hat einen ziemlichen Overhead, sodaß sie sich nicht zum Speichern kleiner Happen eignet. |
AW: Effizienz des Speichermanagers
Zitat:
Code:
Falls die Zahlen etwas anders sind: Ich hab in meinem Benchmark-Code etwas Unsinn gefunden und ausgebessert... war halt schnell mit Copy & Paste-Programmierung zusammengestrickt :oops:. Allerdings wurden trotzdem alle unter den gleichen Umständen getestet.
B+ 1000000 Inserts: 447
B+ 1000000 Locates: 280 B+ 500000 Removes: 201 B+ 500000 Reinserts: 241 SL 1000000 Inserts: 713 SL 1000000 Locates: 212 SL 500000 Removes: 324 SL 500000 Reinserts: 347 AVL 1000000 Inserts: 623 AVL 1000000 Locates: 259 AVL 500000 Removes: 73 AVL 500000 Reinserts: 91 RB 1000000 Inserts: 1345 RB 1000000 Locates: 849 RB 500000 Removes: 1050 RB 500000 Reinserts: 721 Das Ergebnis finde ich interessant, weil man, wenn man bei Google nach Empfehlungen für balancierte Bäume sucht, eigentlich immer liest, dass AVL-Bäume bei häufigen Einfüge- und Löschoperationen wegen häufigem Rebalancing nicht so gut geeignet seien, insbesondere im Vergleich zu Rot-Schwarz-Bäumen. Mich wundert es allerdings, dass die Removes hier schneller zu gehen scheinen als die Locates. Vielleicht sollte langsam mal ein repräsentativerer Test her... |
AW: Effizienz des Speichermanagers
Kannst Du den Testcode posten? Wie verhalten sich die Datenstrukturen beim Einfügen aufsteigender bzw. absteigender Schlüssel?
|
AW: Effizienz des Speichermanagers
Der Testcode ist ziemlich hässlich und mit heißer Nadel gestrickt, aber gut:
Delphi-Quellcode:
Analog sieht er für die anderen Datenstrukturen aus.
const
c = 500000; begin Tree := TABTree.Create; t0 := GetTickCount64(); for i := 1 to c do Tree.Insert(i,i); for i := 2*c downto c+1 do Tree.Insert(i,i); t1 := GetTickCount64(); for i := 1 to c do Tree.Locate(i); for i := 2*c downto c+1 do Tree.Locate(i); t2 := GetTickCount64(); for i := c - c div 2 to c do Tree.Remove(i); for i := c + c div 2 downto c do Tree.Remove(i); t3 := GetTickCount64; for i := c - c div 2 to c do Tree.Insert(i,i); for i := c + c div 2 downto c do Tree.Insert(i,i); t4 := GetTickCount64; Memo1.Append(Format('B+ %d Inserts: %d', [2*c, t1-t0])); Memo1.Append(Format('B+ %d Locates: %d', [2*c, t2-t1])); Memo1.Append(Format('B+ %d Removes: %d', [c, t3-t2])); Memo1.Append(Format('B+ %d Reinserts: %d', [c, t4-t3])); Tree.Free; Also es werden immer zur Hälfte aufsteigend und zur Hälfte absteigend Daten eingefügt, gesucht, gelöscht etc. |
AW: Effizienz des Speichermanagers
Zitat:
Hmmm... oder gleicht sich das aus? :gruebel: Teste doch mal zusätzlich mit Random-Werten. Achtung: Die Skiplist ist nicht ohne weiteres geeignet zur Aufnahme identischer Werte, müsstest du prüfen. Alternativ erstellst Du dir eine Liste mit 1Mio Einträgen, mischst die mit Fisher-Yates und fügst dann die Werte ein. Ist ja auch Random. Ich teste jedenfalls immer so. |
AW: Effizienz des Speichermanagers
Zitat:
Meinen eigenen RB-Tree verwende ich deshalb so gerne, da er ein wenig schneller bei den Masseninserts ist als der AVL-Tree. Massen-Inserts habe ich immer - Locates und andere Operationen treten oft nicht so gehäuft auf. Daher denke ich, dass für die Praxis ein RB-Tree oder B-Tree etwas besser als der AVL-Tree sein sollte. Den AVL-Tree der FCL zu schlagen ist aber nicht leicht. Zitat:
Die sorted Inserts finde ich recht wichtig, da man damit Masseninserts oft wesentlich beschleunigen kann. Am Memory-Manager kann man natürlich auch viel herumspielen... Bei Single-Threaded Sachen hat mir das keinen wirklichen Vorteil gebracht - da poolt der Speichermanager oft selber gut genug. Sobald aber Multi-Threading ins Spiel gekommen ist, ist bei mir Pooling richtig wichtig geworden. |
AW: Effizienz des Speichermanagers
Kurze Zwischenfrage: Müssen die Elemente eigentlich sortiert sein? Braucht man doch gar nicht so häufig.
|
AW: Effizienz des Speichermanagers
Zitat:
wie Einfügen in chaotischer Reihenfolge. Bei der Implementierung von Containern hat man an manchen Stellen die Wahl, ob man Sachen eher links oder rechts einfügt, und ob man zuerst auf < oder > prüft. Damit kann man dem Container eine Lieblings-Reihenfolge einprogrammieren, die dann schneller funktioniert. |
AW: Effizienz des Speichermanagers
Zitat:
Nebenbei kannst Du hier keine Faktoren angeben, denn z.B. das Einfügen in eine sortierte Liste ist (ggü einer unsortierten Liste) nicht gerne mal 3x so schnell, sondern -je nach Anzahl- auch ruhig mal 10000000000x so schnell. Zitat:
Die Frage lautet also: Wozu muss die Liste sortiert sein? Der Namenlose hat den B+-Baum im Rahmen einer Lehrveranstaltung Programmiert (klasse), aber im Allgemeinen haben diese Strukturen keine sonderliche Verbreitung, weil man eben nicht sonderlich oft sortierte Listen benötigt. Ach, wurde ein (binomial) Heap schon probiert? Müsste ich mal raussuchen... |
AW: Effizienz des Speichermanagers
Zitat:
Zitat:
Für einen umfangreichen Test muss natürlich beides getestet werden, das ist klar. |
AW: Effizienz des Speichermanagers
Zitat:
Zitat:
Schnellste sortierte Liste (bezüglich Insert/Update/Delete/Locate/)? Interessante Aufgabenstellung. |
AW: Effizienz des Speichermanagers
Liste der Anhänge anzeigen (Anzahl: 1)
So, ich habe jetzt noch mal einen saubereren Test in einem neuen Projekt geschrieben.
Delphi-Quellcode:
Einige Beobachtungen:
B+ Tree
Ascending linear (1000000) Insert: 451 ms Locate: 273 ms Remove: 543 ms Descending linear (1000000) Insert: 661 ms Locate: 332 ms Remove: 340 ms Random (1000000) Insert: 1858 ms Locate: 1006 ms Remove half: 810 ms Reinsert half: 976 ms AVL Tree Ascending linear (1000000) Insert: 340 ms Locate: 270 ms Remove: 255 ms Descending linear (1000000) Insert: 314 ms Locate: 274 ms Remove: 265 ms Random (1000000) Insert: 1604 ms Locate: 1499 ms Remove half: 946 ms Reinsert half: 933 ms Skiplist Ascending linear (1000000) Insert: 370 ms Locate: 204 ms Remove: 180 ms Descending linear (1000000) Insert: 233 ms Locate: 255 ms Remove: 337 ms Random (1000000) Insert: 3095 ms Locate: 3490 ms Remove half: 1758 ms Reinsert half: 1818 ms RB Tree Ascending linear (1000000) Insert: 865 ms Locate: 387 ms Remove: 1319 ms Descending linear (1000000) Insert: 1043 ms Locate: 383 ms Remove: 1643 ms Random (1000000) Insert: 3282 ms Locate: 1682 ms Remove half: 2139 ms Reinsert half: 1780 ms
Source Code für den Benchmark ist im Anhang, falls jemand seine eigenen Implementierungen testen will. Am Anfang des Programms sind $DEFINES für die zu testenden Klassen, sodass ihr das Programm auch leicht kompilieren könnt, wenn ihr nicht alle Units habt (z.B. unter Delphi). Mitgeliefert wird nur die Skiplist von alzaimar. |
AW: Effizienz des Speichermanagers
Ich würde noch vergleichen, wie sich die Strukturen bei kleineren Datenmengen verhalten. Hier macht sich der generelle Overhead stärker bemerkbar. Bei sehr kleinen Listen ist ein einfaches sortiertes Array oft schneller als ein Baum, weil eigentlich kein Overhead vorhanden ist.
Man muss wirklich aufpassen ob und wann man diese 'Ungetüme' (300+ Zeilen für eine 'einfache Liste' ist schon happig) verwenden sollte und wann nicht. Alzaimar hat ja hier einen ganz netten Vergleich geschrieben, der das Verhalten bei kleineren Datenmengen analysiert, da kommt sogar der AVL-Baum und die Skplist vor. Allerdings ist dort die Skiplist viel schneller als der AVL-Baum. Vielleicht, weil es auch viel weniger Daten sind(?) |
AW: Effizienz des Speichermanagers
Zitat:
Ich habe mal -O1 und -O3 getestet und dazu das ganze auch mal mit Delphi7 mit FastMM ausprobiert: FPC 2.7.1 -O1 AVL Tree AL (1000000) I:203 L:109 R:94 ms DL (1000000) I:172 L:94 R:109 ms RD (1000000) I:577 L:452 ms Remove half: 344 ms Reinsert half: 374 ms Skiplist AL (1000000) I:265 L:188 R:124 ms DL (1000000) I:125 L:203 R:250 ms RD (1000000) I:1216 L:1389 ms Remove half: 671 ms Reinsert half: 733 ms RBMap (ohne Pooling) AL (1000000) I:312 L:171 R:125 ms DL (1000000) I:312 L:172 R:140 ms RD (1000000) I:842 L:640 ms Remove half: 406 ms Reinsert half: 452 ms RBMultiMap (Pooled) AL (1000000) I:203 L:109 R:62 ms DL (1000000) I:203 L:109 R:78 ms RD (1000000) I:593 L:515 ms Remove half: 312 ms Reinsert half: 374 ms FPC 2.7.1 -O3 AVL Tree AL (1000000)I: 203 L: 93 R: 110 DL (1000000)I: 187 L: 93 R: 110 RD (1000000)I: 592 L: 468 RH: 328 RIH: 406 Skiplist AL (1000000)I: 249 L: 94 R: 93 DL (1000000)I: 125 L: 109 R: 172 RD (1000000)I: 1357 L: 1326 RH: 655 RIH: 749 RBMap AL (1000000)I: 218 L: 125 R: 109 DL (1000000)I: 219 L: 125 R: 109 RD (1000000)I: 764 L: 609 RH: 390 RIH: 421 RBMultiMap (Pooled) AL (1000000)I: 109 L: 62 R: 47 DL (1000000)I: 125 L: 62 R: 47 RD (1000000)I: 562 L: 452 RH: 297 RIH: 327 Delphi 7 - FastMM AVL Tree AL (1000000)I: 125 L: 93 R: 94 DL (1000000)I: 140 L: 94 R: 78 RD (1000000)I: 515 L: 468 RH: 312 RIH: 359 Skiplist AL (1000000)I: 218 L: 203 R: 62 DL (1000000)I: 78 L: 188 R: 234 RD (1000000)I: 1154 L: 1373 RH: 639 RIH: 687 RBMap AL (1000000)I: 141 L: 46 R: 47 DL (1000000)I: 125 L: 62 R: 47 RD (1000000)I: 515 L: 437 RH: 296 RIH: 312 RBMultiMap (Pooled) AL (1000000)I: 94 L: 62 R: 31 DL (1000000)I: 94 L: 62 R: 31 RD (1000000)I: 515 L: 437 RH: 281 RIH: 296 Fazit: - Für Inserts ist die Skiplist Descending Linear am schnellsten, allerdings leidet der Lookup deutlich darunter - An den AVL-Tree komm ich unter FPC ohne Node-Pooling kaum heran. Unter Delphi kann FastMM die Lücke recht gut ausbügeln - Gepooltes RedBlack und AVL-Tree liegen recht eng beieinander. Vom Algorithmus her sind beide in der Praxis wohl ziemlich gleich. Den Unterschied machen wohl nur ein paar Tricks in der Implementierung aus - Beim schnellsten Container liegen FPC und Delphi von der Geschwindigkeit in derselben Region. Der Speichermanager unter Delphi hilft eingen weniger optimierten Containern deutlich - Wegen der Messtoleranz sind 1000000 Nodes fast schon zu wenig... |
AW: Effizienz des Speichermanagers
Zitat:
Wenn Du weißt, das 1 Mio Einträge ungefähr 100ms brauchen, dann misst Du einfach 10 Mio / N mal, wobei N die Anzahl der Samples sind. Dann bist Du pro Test bei ca. 1 Sekunde und im sicheren Bereich (von wegen Messfehler etc.) |
AW: Effizienz des Speichermanagers
Zitat:
Zum Glück sind bei Pascal die Kompilierzeiten kurz, da kann man sich solche „Ungetüme“ wenigstens noch leisten... |
AW: Effizienz des Speichermanagers
Zitat:
Kleine Info noch nebenbei: in 2.7.1 gibt as auch noch -O4 und für -O3 könntest du noch -Oodeadvalues(damit fehlt nur noch das Field Reordering für -O4) und -Oodeadstore bzw. für -O4 nur -Oodeadstore noch einschalten (eine aktuelle 2.7.1 vorausgesetzt :mrgreen: ). Wenn du -Oodeadstore, -Oodeadvalues und -Ooconstprop unter -O- bis -O2 einsetzen willst, musst du auch noch -Oodfa aktivieren. Gruß, Sven |
AW: Effizienz des Speichermanagers
Zitat:
Was ich meinte war: Wenn eine Datenstruktur bei 10 Mio Einträgen wunderbar performant ist, aber mein Anwendungsfall nur 10 Elemente beinhaltet (das aber 1 Mio x pro Sekunde) muss man sich 2x überlegen, ob meine Datenstruktur die geeignete ist. Compilerzeiten und Dateigrößen sind da (für mich) zweitrangig. Boyer-Moore z.B. ist bekanntermaßen der schnellste Stringmatcher. Aber nur bei großen Alphabeten und langen zu suchenden Strings in noch längeren Zeichenketten. |
AW: Effizienz des Speichermanagers
[QUOTE=JamesTKirk;1255922]
Zitat:
Ich habe die Unit jetzt mal ins Projekt reingenommen: FPC 2.7.1 -O1 AVL Tree AL (1000000)I: 234 L: 141 R: 156 DL (1000000)I: 202 L: 156 R: 156 RD (1000000)I: 656 L: 530 RH: 374 RIH: 437 FPC 2.7.1 -O3 AVL Tree AL (1000000)I: 203 L: 94 R: 109 DL (1000000)I: 171 L: 94 R: 125 RD (1000000)I: 577 L: 436 RH: 328 RIH: 390 Das sollte jetzt passen. -O4 klingt interessant... mal schauen... |
AW: Effizienz des Speichermanagers
Zitat:
Lokalität und wenig Speicherverwaltung FTW :wink: |
AW: Effizienz des Speichermanagers
Eine Speicherseite (4096 Bytes) erscheint mir recht viel. Bei der Größe würde es mich doch wundern, wenn ein Baum nicht schneller wäre. Ich kann mir vorstellen, dass Arrays bis ~100 oder vielleicht 200 Elemente (sagen wir mal Ints) überlegen sind.
|
AW: Effizienz des Speichermanagers
Naja, wenn man etwas schielt, ist die binäre Suche in einem sortierten Vector/Array eine Suche in einem Binärbaum. Damit hat man maximal log_2(4096) = 12 Schritte, um einen Eintrag zu finden.
Den Vorteil hat man dann aber nur beim Suchen, beim Einfügen/Löschen können Kopieroperationen teuer werden. Dabei kommt es natürlich auch darauf an, wie die anderen Datenstrukturen verwaltet werden. Ein Baum, der durch seine Verteilung im Speicher auch nur einen Pagefault mehr auslöst, sollte dadurch für mostly-read-Fälle deutlich unattraktiver werden :mrgreen: |
AW: Effizienz des Speichermanagers
Das Suchen ist wohl auch eher nicht das Problem. Aber das Einfügen und Löschen eines Elements ist beim Array O(n), weil die Elemente im Speicher umkopiert werden müssen.
|
AW: Effizienz des Speichermanagers
Zitat:
"Big Oh ist nicht alles. Es gibt auch noch diese häßliche Konstante" |
Alle Zeitangaben in WEZ +1. Es ist jetzt 19:19 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz