thesis - Institut für Informatik

Werbung
Leopold-Franzens-Universität Innsbruck
Institut für Informatik
Datenbanken und Informationssysteme
Evaluierung von sortierten
hauptspeicherbasierten Indexstrukturen
Bachelor-Arbeit
Christof Rinner
betreut von
Dipl.-Ing. Robert Binna
Innsbruck, 25. Juni 2012
Zusammenfassung
Steigende Arbeitsspeicherkapazitäten ermöglichen es Datenbanksystemen, welche zuvor ausschließlich auf Festplatten zur Datenverwaltung
gesetzt haben, den Großteil ihrer Bestandteile in den schnelleren Arbeitsspeicher zu verlagern. Um eine schnelle Anfrageverarbeitung zu
ermöglichen, werden in nahezu allen Systemen spezielle Indexstrukturen eingesetzt, welche einen geordneten Zugriff beschleunigen. Mit dem
Schwenk vom Sekundärspeicher zum Hauptspeicher als primäres Speichermedium haben sich auch die Anforderungen an diese Indexstrukturen in Bezug auf verringerte Latenzzeiten und Speicherhierarchien
grundlegend geändert. Daher ist das Ziel dieser Arbeit die theoretische
und experimentelle Evaluierung von Indexstrukturen, welche einen sortierten Zugriff innerhalb des Hauptspeichers unterstützen. Zusätzlich zu
den bekannten Indexstrukturen wird eine neue Variante der Komprimierung beim B-Baum mit Differenzwerten vorgestellt.
Abstract
Increasing main memory capacities allow for major parts of database
systems to be moved from hard disc into the much faster main memory.
To support a fast and efficient query execution almost all systems use
index structures to speed up point- as well as range queries. With the
shift from disc based to main memory based database systems also the
demands for order-preserving index structures shifted. Besides the reduced latencies also the different memory hierarchies have to be considered.
Therefore the goal of this thesis is the theoretical and experimental evaluation of main memory index structures which feature ordered access
pattern. Beside known datastructures a new approach for compressing
b-trees with difference values is presented.
Inhaltsverzeichnis
1 Einleitung
1
2 Binäre Suchbäume
2
2.1
2.2
2.3
Natürlicher binärer Suchbaum . . . . . . . . . . . . . . . .
2
2.1.1
Such-Operation . . . . . . . . . . . . . . . . . . . .
3
2.1.2
Einfüge-Operation . . . . . . . . . . . . . . . . . .
3
2.1.3
Lösch-Operation . . . . . . . . . . . . . . . . . . .
4
2.1.4
Durchlaufen des Suchbaums . . . . . . . . . . . . .
5
Selbstbalancierende Bäume . . . . . . . . . . . . . . . . .
6
2.2.1
Rot-Schwarz-Baum . . . . . . . . . . . . . . . . . .
6
2.2.2
AVL-Baum . . . . . . . . . . . . . . . . . . . . . .
9
2.2.3
Scapegoat-Baum . . . . . . . . . . . . . . . . . . . 10
Verwandte Arbeiten . . . . . . . . . . . . . . . . . . . . . 11
2.3.1
AA-Baum und LLRB . . . . . . . . . . . . . . . . 11
2.3.2
T-Baum . . . . . . . . . . . . . . . . . . . . . . . . 11
3 Mehrwegbäume
3.1
B-Baum . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.1.1
3.2
3.3
13
Operationen . . . . . . . . . . . . . . . . . . . . . . 14
Komprimierung von IDs und Schlüsseln . . . . . . . . . . 15
3.2.1
Statische Komprimierung im B-Baum . . . . . . . 15
3.2.2
Dynamische Komprimierung mit Differenzen . . . 15
Verwandte Arbeiten . . . . . . . . . . . . . . . . . . . . . 17
II
INHALTSVERZEICHNIS
3.3.1
CSB+ -Baum . . . . . . . . . . . . . . . . . . . . . 17
4 Evaluierung der Bäume
19
4.1
Speicherverbrauch . . . . . . . . . . . . . . . . . . . . . . 19
4.2
Such-Operation . . . . . . . . . . . . . . . . . . . . . . . . 20
4.3
Einfügen und Löschen . . . . . . . . . . . . . . . . . . . . 20
5 Benchmarks
21
5.1
Testsystem . . . . . . . . . . . . . . . . . . . . . . . . . . 22
5.2
Untersuchte Datenstrukturen . . . . . . . . . . . . . . . . 22
5.3
5.2.1
Natürlicher binärer Suchbaum
. . . . . . . . . . . 22
5.2.2
Rot-Schwarz-Bäume . . . . . . . . . . . . . . . . . 22
5.2.3
B-Baum . . . . . . . . . . . . . . . . . . . . . . . . 26
Ausgwählte Resultate . . . . . . . . . . . . . . . . . . . . 27
6 Zusammenfassung und Ausblick
30
Appendix
31
A.1 Implementierungsdetails . . . . . . . . . . . . . . . . . . . 31
A.1.1 Komprimierung der Farbinformation des Rot-SchwarzBaums . . . . . . . . . . . . . . . . . . . . . . . . . 31
A.2 Speicherverwaltung . . . . . . . . . . . . . . . . . . . . . . 31
A.2.1 Anlegen des Speichers und von Datenstrukturen . 32
Literaturverzeichnis
36
III
Kapitel 1
Einleitung
An Datenstrukturen im Hauptspeicher werden andere Anforderungen
gestellt, als an solche die für die Festplatte ausgelegt sind. B-Bäume sind
bei konventionellen Datenbanken Standard, da diese besonders geeignet
für Festplatten mit ihren hohen Latenzzeiten beim Zugriff sind. Diese
Latzenzzeit fällt bei hauptspeicherbasierten Indexstrukturen weg, aber
auch hier gibt es die Gegebenheiten zu beachten. Es ist anzunehmen,
dass auch hier der Speicherzugriff der limitierende Faktor ist. Deshalb ist
bei diesen Datenstrukturen die Cache-Optimierung ein wichtiger Punkt.
Durch die Verfügbarkeit immer größerer Arbeitsspeicher werden Hauptspeicherdatenbanken immer verbreiteter. An die verwendeten Datenstrukturen werden so neue Anforderungen gestellt. Beispielsweise soll
es möglich sein Daten persistent zu halten. Das setzt voraus, dass keine
absoluten Adressen verwendet werden. Um dies zu erreichen wird eine
eigene Speicherverwaltung entwickelt, welche die verwendeten relativen
Adressen auflöst.
Um zu einem objektiven Ergebnis zu kommen, werden die Datenstrukturen nicht nur einer theoretischen Evaluierung unterzogen, sondern auch
experimentell auf ihr Laufzeitverhalten hin untersucht.
Die Arbeit gliedert sich in einen theoretischen und praktischen Bereich.
Die Kapitel 2 und 3 gehen auf die verbreiteten Datenstrukturen ein. Im
Anschluss wird die Evaluierung der Datenstrukturen vorgenommen. Das
Kapitel 5 umfasst die Benchmarks. Abgeschlossen wird die Arbeit von
einer Zusammenfassung und einem Ausblick auf die weitere Entwicklung
in diesem Themenbereich.
1
Kapitel 2
Binäre Suchbäume
In diesem Kapitel werden Suchbäume mit zwei Nachfolgern vorgestellt.
Zuerst wird der natürliche Binärbaum mit seinen Operationen erklärt.
Weiters wird auf drei verschiedene selbstbalancierende Binärbäume eingegangen. Bei den behandelten Datenstrukturen handelt es sich um den
Rot-Schwarz-Baum, den AVL-Baum und den Scapegoat-Baum.
2.1
Natürlicher binärer Suchbaum
Der hier vorgestellte Baum bietet im Gegensatz zu allen anderen in dieser Arbeit vorgestellten Datenstrukturen keine Maßnahmen zur Balancierung. Daraus resultiert eine schlechte worst-case-Laufzeit. Die Operationen der in Unterabschnitt 2.2 erläuterten balancierten Bäume bauen
jedoch auf denen des natürlichen Baums auf. Deshalb werden diese im
Folgenden vorgestellt.
Die Abbildung 2.1 zeigt einen Binärbaum mit vier Schlüsseln.
2
KAPITEL 2. BINÄRE SUCHBÄUME
20
17
25
18
nil
nil
nil
nil
nil
Abbildung 2.1: Binärer Suchbaum.
2.1.1
Such-Operation
Aufgrund der vorhandenen Ordnung der Schlüssel sind jeweils im linken
Teilbaum Schlüssel kleiner bzw. im rechten Teilbaum größer als im Vaterknoten angeordnet. Daher muss man nur den Teilbaum untersuchen
in welchem der gesuchte Schlüssel liegen müsste. Liegt ein perfekt balancierter Baum vor (der linke und rechte Teilbaum jedes Knoten besitzt
die exakt selbe Höhe), teilt man die zu betrachtende Schlüsselmenge bei
jedem Schritt. Dadurch ergibt sich eine Komplexität von O(log2 (n)) bei
n Knoten. Im Vergleich dazu hat die Suche in unsortierten Listen eine
Komplexität von O(n), weil um sicherzugehen ob ein Element vorhanden
ist, möglicherweise alle Schlüssel betrachtet werden müssen.
Beim Suchen eines Schlüssels beginnt man bei der Wurzel. Ist die Wurzel
ein Blatt (leerer Baum) ist die Suche bereits beendet und der Schlüssel
ist nicht im Baum enthalten. Ansonsten vergleicht man den gesuchten
Schlüssel mit dem Schlüssel des Knotens. Sollte der Wert kleiner als dieser sein durchsucht man den linken Teilbaum, sonst den rechten. Dieser
Vorgang wird wiederholt bis entweder der gesuchte Schlüssel gefunden
wird oder man zu einem Blatt gelangt. Das Listing 2.1 zeigt diesen Vorgang in Pseudo-Code.
2.1.2
Einfüge-Operation
Beim Einfügen eines Schlüssels wird zunächst die Suche verwendet, um
das Blatt in dem der Schlüssel eingefügt werden soll zu bestimmen.
Findet man jedoch den Schlüssel wird die Operation abgebrochen. Ansonsten wird das Blatt durch den einzufügenden Schlüssel ersetzt. Die
beiden Kinder sind dann jeweils ein Blatt.
3
KAPITEL 2. BINÄRE SUCHBÄUME
Die Abbildung 2.2 zeigt die Einfügeoperation des Knotens 18. Links wird
das Blatt, welches im ersten Schritt als Einfügepunkt bestimmt wurde,
rot gekennzeichnet.
2.1.3
Lösch-Operation
Zum Löschen wird wiederum der zu löschende Schlüssel gesucht. Wird
dieser nicht gefunden bricht der Vorgang ab. Ansonsten können drei
Fälle auftreten:
Knoten hat keinen Sohn Knoten kann einfach entfernt werden.
Knoten hat einen Sohn Lösche den Knoten und ersetze ihn durch
seinen Sohn.
Knoten hat zwei Nachfolger Suche den kleinsten Schlüssel im rechten Teilbaum des zu löschenden Knoten. Dieser Knoten entspricht
dem am weitesten links stehenden Knoten im Teilbaum. Dieser ersetzt den zu löschenden. Diese Vorgehensweise wird in Abbildung
2.3 anhand des Löschens von Schlüssel 17 gezeigt.
Listing 2.1: Pseudo-Code für die rekursive Suche in einem Binärbaum.
f i n d ( node , key ) {
i f ( node == NIL | | key == node . key )
return node ;
i f ( key < node . key )
return f i n d ( node . l e f t , key )
e l s e return f i n d ( node . r i g h t , key ) ;
}
4
KAPITEL 2. BINÄRE SUCHBÄUME
20
20
17
nil
25
nil
nil
17
nil
25
18
nil
nil
nil
nil
nil
Abbildung 2.2: Der rechte Baum zeigt den linken Baum nach dem
Einfügen des Schlüssels 18.
20
17
14
20
25
14
18
25
18
Abbildung 2.3: Löschen eines Knoten mit zwei Kindern.
2.1.4
Durchlaufen des Suchbaums
Will man die Elemente eines Baumes durchlaufen, gibt es verschiedene
Suchstrategien. Wenn die Schlüssel in aufsteigender/absteigender Reihenfolge durchlaufen werden sollen, kommt die symmetrische Reihenfolge (auch als Inorder bezeichnet) zur Anwendung. Diese kann durch den
rekursiven Algorithmus in Listing 2.2 implementiert werden:
5
KAPITEL 2. BINÄRE SUCHBÄUME
Listing 2.2: Pseudo-Code zum Durchlaufen eines Baumes in symmetrischer Reihenfolge.
s y m e t r i c t r a v e r s a l ( node ) {
i f ( node == NIL )
return ;
s y m e t r i c t r a v e r s a l ( node . l e f t ) ;
p r i n t ( node . key ) ;
s y m e t r i c t r a v e r s a l ( node . r i g h t ) ;
}
Der Parameter beim Aufruf der Funktion entspricht dem Startpunkt
des Durchlaufs. Für eine Durchlauf des gesamten Baumes wählt man
den Wurzelknoten.
2.2
Selbstbalancierende Bäume
Da viele Operationen des binären Suchbaums auf der Such-Operation
aufbauen ist eine möglichst hohe Geschwindigkeit dieser Operation anzustreben. Grundsätzlich hat diese eine Komplexität von O(h), wobei h
für die Höhe des Baums steht.
Da binäre Suchbäume keine eindeutige Darstellung haben, können die
Reihenfolge des Einfügens und Löschens Einfluss auf den Aufbau des
Baums haben. Abbildung 2.4 zeigt zwei Bäume, die aus denselben Schlüsseln
bestehen, jedoch wurden diese in verschiedener Reihenfolge eingefügt.
Dies hat zur Folge, dass sich die Höhe der Bäume unterscheiden kann
und so folglich auch die Laufzeit der Such-Operation. Im Falle der linken
Abbildung spricht man von einem degenerierten Baum. Die Komplexität
der Suche in diesem Fall entspricht derer der unsortierten Liste: O(n). In
diesem Abschnitt werden Bäume behandelt, welche unabhängig von der
Reihenfolge der Update-Operationen, also dem Einfügen und Löschen,
nicht degenerieren können.
2.2.1
Rot-Schwarz-Baum
Rot-Schwarz-Bäume wurden von Rudolf Bayer erfunden, der sie als
“symmetric binary B-Trees” bezeichnete. [Bay72] Der nunmehr gebräuchliche
Name wurde von Leo J. Guibas und Robert Sedgewick eingeführt. [GS78]
Der Rot-Schwarz-Baum unterstützt alle Operationen des Suchbaums.
Die Implementierung der Einfüge- und Lösch-Operation baut auf den
6
KAPITEL 2. BINÄRE SUCHBÄUME
2
4
3
2
4
5
3
7
5
7
Abbildung 2.4: Einfügereihenfolge 2, 3, 4, 5, 7 bzw.4, 2, 5, 7, 3
Operationen des natürlichen Binärbaums auf, jedoch sorgen diese zusätzlich
dafür, dass der Baum balanciert bleibt. Die maximale Höhe ist 2·log2 (n + 1).
Das bedeutet, dass ein Pfad zu einem Blatt maximal doppelt so lang,
als alle anderen sein kann. Sowohl der average als auch der worst-case
der Suche beläuft sich somit auf O(log2 (n)).
Jeder Knoten eines Rot-Schwarz-Baums hat ein zusätzliches Attribut,
welches die Farbe des Knotens (entweder rot oder schwarz) speichert.
Ein Rot-Schwarz-Baum muss die in [CLRS09] definierten Bedingungen
erfüllen:
1. Jeder Knoten ist entweder rot oder schwarz.
2. Der Wurzelknoten ist schwarz.
3. Jedes Blatt ist schwarz.
4. Beide Kinder eines roten Knotens sind schwarz.
5. Alle einfachen Pfade von einem Knoten zu den Blättern haben
gleich viele schwarze Knoten.
Die Abbildung 2.5 zeigt einen Baum, der alle genannten Bedingungen
erfüllt. Diese können durch Update-Operationen verletzt werden. Darum müssen diese nach einer solchen Operation überprüft werden und
gegebenenfalls durch Rotationen und Änderungen der Farbe wiederhergestellt werden.
7
KAPITEL 2. BINÄRE SUCHBÄUME
20
17
8
25
19
22
28
nil nil nil nil nil nil nil nil
Abbildung 2.5: Ein Rot-Schwarz-Baum, der alle Eigenschaften erfüllt.
y
x
y
x
3
1
2
1
2
3
Abbildung 2.6: Ausgleich der Höhendifferenz im linken Baum mittels
einer Rechtsrotation.
Solch eine Rotation wird in Abbildung 2.6 gezeigt. Dabei handelt es sich
um eine Rechtsrotation, Linksrotationen verlaufen äquivalent. Bei 1,2
und 3 handelt es sich um beliebige Teilbäume.
Eine Rotation hat eine Komplexität von O(1), da nur Pointer geändert
werden müssen.
Die Einfüge-Operation Das Einfügen eines neuen Knotens basiert
auf dem Einfügen des binären Suchbaums. Der neue Knoten wird rot
gefärbt. Die Kinder des neuen Knotens sind beide schwarz.
Danach wird eine Operation aufgerufen, welche überprüft, ob die Bedingungen erfüllt werden und wenn nötig Rotationen und Farbänderungen
vornimmt. Bei einem Einfügevorgang kann es zu maximal 2 Rotationen
kommen. [CLRS09, ]
Durch das Einfügen eines neuen Knotens können die Bedingungen 2
und 4 verletzt werden. Die Bedingung 2 wird nur verletzt wenn der neue
8
KAPITEL 2. BINÄRE SUCHBÄUME
Knoten die Wurzel ist. Diese ist dann wider den Bedingungen rot.
Bedingung 4 wird verletzt, wenn der Vater des neuen Knotens rot ist.
Die Lösch-Operation Wie schon beim Einfügen wird auch beim
Löschen eines Knotens nach den Regeln des natürlichen Binärbaums vorgegangen und anschließend die Farbeigenschaften überprüft. Nach dem
Löschen können die Eigenschaften 2, 4 und 5 verletzt sein. Zum Auflösen
der Konflikte werden höchstens 3 Rotationen benötigt. Es kann neben den Rotationen auch nötig sein Schlüssel zu verschieben. (maximal
O(log2 (n))-mal) Die Gesamtkomplexiät beträgt ebenfalls O(log2 (n)).[CLRS09]
Komprimierung der Farbinformation Da man mit einem 64-BitPointer 264 Byte addressieren kann, würden im theoretischen Fall, dass
der gesamte Arbeitsspeicher mit Knoten von 24 Byte (2 Nachfolger und
der Schlüssel mit jeweils 8 Byte) befüllt ist, Bits ungenützt bleiben. Diese können zum mitspeichern der Balanceinformationen genutzt werden.
Das heißt, es wird kein zusätzlicher Speicherplatz benötigt und durch
die kleinere Knotengröße wird die Anzahl der im Cache Platz findenden
Knoten erhöht. Das Decodieren der Variable aus dem Pointer kann sich
aber auch negativ auf die Performance auswirken. Beispielsweise liefert
ie Implementierung des Rot-Schwarz-Baums in der boost-Bibliothek1
einen Schalter mit, der diese Möglichkeit bietet.
2.2.2
AVL-Baum
AVL-Bäume zählen zu den höhenbalancierten Bäumen. Ein Suchbaum
gilt als AVL-Baum, wenn sich die Höhe der Teilbäume eines jeden Knotens höchstens um eins unterscheidet. Jeder Knoten hat ein Attribut,
welches die Höhendifferenz der beiden Teilbäume speichert. Dieses Attribut kann die Werte 0, 1 und -1 annehmen. Null symbolisiert, dass die
Teilbäume gleich hoch sind. Eins bedeutet, dass der rechte Teilbaum um
eine Ebene höher ist, minus eins um eine niedriger. [OW02]
Bei einer Operation welche die Struktur des Baumes ändert, also entweder Einfügen oder Löschen, führt man zunächst die eigentliche Aktion
aus. Anschließend wird überprüft, ob die Balanciertheit der Teilbäume
erfüllt ist. Eine Verletzung der Bedingungen liegt vor, wenn der Höhenunterschied
der Teilbäume 1 bzw. -1 überschreitet. Es werden höchstens O(log2 (n))
Rotationen benötigt, um die Balance wiederherzustellen.
1
http://www.boost.org
9
KAPITEL 2. BINÄRE SUCHBÄUME
2.2.3
Scapegoat-Baum
Scapegoat-Bäume unterscheiden sich von den anderen selbstbalancierenden binären Suchbäumen dahingehend, dass die Knoten keine zusätzlichen
Informationen beinhalten, um die Balanciertheit zu gewährleisten. RotSchwarz-Bäume benötigen ein zusätzliches Bit, um die Farbe des Knoten zu speichern und AVL-Bäume 2 Bits für die Höhendifferenz. Die
einzigen zusätzlichen Daten die benötigt werden sind zwei Werte. Diese
Werte sind zum einen die Anzahl der Knoten des Baums und zum anderen die maximale Anzahl der Knoten seit der Baum zum letzten mal
komplett neu aufgebaut wurde. Diese Werte werden im Weiteren als size
und max size bezeichnet.[GR93]
Die Erfinder des Scapegoat-Baums Igal Galperin und Ronald L. Rivest
verwenden zur Beschreibung der Balance die Begriffe α-weight-balanced
und α-height-balanced. Bei dieser Definition ist α ein Wert im Intervall
[0.5,1], der die Strenge der Balancierung angibt.
Ein Knoten ist α-weight-balanced, wenn die folgenden zwei Bedingungen
zutreffen:
• size(node.lef t) <= α · size(node)
• size(node.right) <= α · size(node)
und height-balanced wenn für einen Baum T gilt:
height(T) < hα(size(T )) , wobei hα (n) sich folgendermaßen berechnet:
hα (n) = blog1/α nc
Operationen Die Such-Operation des Scapegoat-Baums entspricht
der des binären Suchbaums, jedoch unterscheiden sich die Einfüge- und
Lösch-Operation.
Beim Einfügen wird size um eins erhöht. Außerdem wird max size auf
das Maximum von max und max size gesetzt. Sollten diese neuen Werte
zu einer Verletzung der Bedingungen führen, wird der neue Knoten als
ein tiefer Knoten bezeichnet. In diesem Fall klettert man den Baum hinauf zur Wurzel und überprüft für jeden Knoten ob er α-weight-balanced
ist. Ist dies nicht der Fall wird der Knoten als scapegoat (Sündenbock)
bezeichnet. Dann wird der Baum unter dem Sündenbock neu aufgebaut,
um die Balance wiederherzustellen. Ist die Wurzel der Sündenbock, muss
der komplette Baum neu aufgebaut werden, weshalb die Komplexität in
diesem Fall O(n) ist. Dieser Fall tritt jedoch sehr selten auf, dadurch
wird die amortisierte Laufzeit mit O(log2 (n)) angegeben.
10
KAPITEL 2. BINÄRE SUCHBÄUME
Will man einen Knoten löschen, geht man wie beim binären Suchbaum
vor und verringert zusätzlich size um eins. Ist size kleiner als α·max size
muss der gesamte Baum neu aufgebaut werden.
2.3
2.3.1
Verwandte Arbeiten
AA-Baum und LLRB
Bei diesen beiden Baumtypen handelt es sich um Varianten des RotSchwarz-Baumes.
Der AA-Baum wurde von Arne Andersson mit dem Ziel einer möglichst
einfachen Impelementierbarkeit erfunden. Beim Rot-Schwarz-Baum sind
bei der Wiederherstellung der Balance 7 verschiedene Fälle zu betrachten. Hingegen wurde dies beim AA-Baum auf 2 Fälle reduziert.[And93]
Erreicht wird dies, indem zu den üblichen Bedingungen des Rot-SchwarzBaums ergänzt wird, dass ein roter Knoten nur das rechte Kind des Vaters sein darf. Wird diese Bedingung verletzt muss eine Rotation durchgeführt werden.
Auch beim LLRB (Left-leaning Red-Black Trees) von Sedgewick [Sed08]
wird eine neue Bedingung eingeführt. Hier darf ein roter Knoten nur das
linke Kind sein.
Außerdem werden die Operationen mit rekursiven Funktionen, welche
leicht verständlich sein sollen, beschrieben. Geschwindigkeit war bei der
Entwicklung nicht so sehr im Fokus wie Einfachheit. Trotzdem haben
die Bäume die gleichen Laufzeiten wie der Rot-Schwarz-Baum.
2.3.2
T-Baum
Eine andere binäre Datenstruktur, welche in vielen Hauptspeicherdatenbanken Verwendung findet, ist der T-Baum.
Ein T-Baum Knoten, wie in Abbildung 2.7, besteht aus maximal 2 Nachfolgern und einer gewissen Anzahl von Schlüsseln, welche direkt im Knoten gespeichert werden. Diese werden wie beim B-Baum (siehe Abschnitt
3.1) sortiert gespeichert. Der T-Baum arbeitet bei der Balancierung mit
denselben Verfahren wie AVL-Bäume. Jedoch besteht ein Knoten aus
mehreren Schlüsseln und somit sind Rotationen seltener notwendig.
Um einen Schlüssel einzufügen wird zunächst der passende Knoten gesucht. Dabei handelt es sich um einen Knoten in dem der neue Schlüssel
zwischen dem kleinsten und größten Schlüssel liegt. Gibt es keinen solchen Knoten, wird der Schlüssel in ein Blatt eingefügt.
11
KAPITEL 2. BINÄRE SUCHBÄUME
Abbildung 2.7: Ein T-Baum Knoten.[LC86]
Kommt es beim Einfügen zu einem Überlauf des Knotens, wird der Knoten eingefügt und dann der kleinste Schlüssel entfernt und im linken Teilbaum im rechtesten Blatt eingefügt. Ist auch dieses voll wird ein neues
Blatt mit diesem Schlüssel erzeugt. Danach muss die Balance überprüft
und möglicherweise wiederhergestellt werden.
Beim Löschen kann es zu einem Unterlauf kommen. Um diesen auszugleichen wird der kleinste Schlüssel des größten Knotens (der Knoten mit
dem größten minimalen Element) im rechten Teilbaum des gelöschten
Knotens hierher verschoben.
Handelt es sich um ein Blatt aus dem der Schlüssel gelöscht wird, kann
dieser gelöscht werden. Sollte es nach dem Entfernen leer sein, oder
bei einem Unterlauf mit seinem Bruder, wenn vorhanden, verschmolzen
werden. Anschließend könnten Rotationen nötig werden.
In dem Artikel [LC86] zeigten die Benchmarks, dass der T-Baum schneller als ein B-Baum ist. Seit der Veröffentlichung 1986 vergrößerte sich
jedoch die Geschwindigkeits bei CPUs stärker als beim Arbeitsspeicher.
So zeigen neuere Untersuchungen, etwa in [RR99], dass dies für moderne
Hardware nicht mehr der Fall ist.
12
Kapitel 3
Mehrwegbäume
3.1
B-Baum
B-Bäume sind eine 1972 von Rudolf Bayer und Edward M. McCreight
vorgestellte Datenstruktur. Obwohl das relationale Datenbankmodell
erst später entwickelt wurde, erwies sich diese Datenstruktur als ideal für den Datenbankzugriff auf Festplatten.
Im Gegensatz zu binären Suchbäumen ist bei B-Bäumen der Verzweigungsgrad viel höher. Dadurch ergibt sich, dass der Baum dementsprechend niedriger ist. Dies bringt mit sich, dass auf dem Weg zu dem gesuchten Knoten weniger verschiedene Knoten angesteuert werden müssen.
Dieser Umstand hat bei Festplatten den Vorteil, dass der teure Spurwechsel seltener erfolgen muss.
15
3 7
0 2
4 6
21 28 32
12 13
17 20
22 25
29 30
34 38 44
Abbildung 3.1: Ein B-Baum der Ordnung 2.
Abbildung 3.2 zeigt einen validen B-Baum der Ordnung k=2. Durch k
wird festgelegt wie viele Schlüssel minimal und maximal in einem Knoten sein müssen. Minimal muss jeder Knoten aus k und maximal aus
13
KAPITEL 3. MEHRWEGBÄUME
2·k Schlüsseln bestehen. Eine Ausnahme bildet hier nur der Wurzelknoten. Dieser darf das Minimum unterschreiten. In der Praxis werden
für Implementierungen, welche für Festplatten ausgelegt sind, Verzweigungsgrade bis zu 2000 verwendet. Dies hängt von der Schlüsselgröße
und der Speicherseitengröße ab.
Genau wie beim binären Suchbaum gilt auch hier, dass jeweils die Schlüssel
im linken Teilbaum kleiner bzw. im rechten größer sein müssen. Natürlich
sind auch die Schlüssel in einem Knoten sortiert angeordnet.
Weiters muss die Höhe aller Teilbäume eines Knoten gleich sein. Diese
Bedingung stellt sicher, dass der Baum immer perfekt balanciert ist.
3.1.1
Operationen
Such-Operation Die Suche funktioniert ähnlich der eines Binärbaums,
jedoch kann es angebracht sein durch die hohe Anzahl an Schlüsseln in
einem Knoten die Binärsuche zu verwenden.
Einfüge-Operation Beim Einfügen eines neuen Schlüssels bestimmt
man zunächst das Blatt, welches den Schlüssel aufnehmen muss. Hat nun
das Blatt nicht seine maximale Größe erreicht (weniger als 2·k Schlüssel),
kann der Schlüssel einfach an der richtigen Stelle im Knoten eingefügt
werden. Ist nun aber der Knoten bereits voll, wird temporär ein Knoten
mit 2·k+1 Schlüsseln angelegt. Dieser wird dann in 2 neue Knoten aufgespalten. Der linke Teilknoten beinhaltet die Schlüssel links vom Median
und der rechte, die rechts vom Median. Der Median wird an den Vaterknoten weitergereicht und dort eingefügt. Ist auch dieser bereits voll,
wiederholt sich der Teilungsschritt bis ein nicht maximal gefüllter Knoten gefunden wird oder man das Wurzelelement erreicht. Falls auch die
Wurzel bereits das Schlüsselmaximum erreicht hat, legt man eine neue
Wurzel an und verknüpt diese mit den beiden aus der ursprünglichen
Wurzel entstandenen Knoten. Nur wenn es nötig ist die Wurzel aufzuspalten wächst der Baum in der Höhe.
15 20 22 27
22
15 20
27 29
Abbildung 3.2: Einfügen des Schlüssels 29 in den linken Baum der Ordnung 2.
14
KAPITEL 3. MEHRWEGBÄUME
Lösch-Operation Nach dem Löschen eines Schlüssels, kann die Anforderungen nach minimal k Elementen verletzt werden. Liegt ein linkes
oder rechtes Geschwister mit k Schlüsseln vor, können die Beiden verschmolzen werden. Ansonsten werden diese temporär verschmolzen und
danach wieder aufgespalten (am Median).
3.2
Komprimierung von IDs und Schlüsseln
Um den Verzweigungsgrad bei gleicher Knotengröße zu erhöhen, kann
der Binärbaum um Komprimierungsmöglichkeiten erweitert werden. Im
Folgenden werden verschiedene Ansätze dafür vorgestellt.
3.2.1
Statische Komprimierung im B-Baum
Da die Datenstrukturen in einem eigenen Speicherbereich, indem die
einzelnen Knoten mit IDs addressiert werden, können statt 64-Bit-IDs
nur soviel Bits verwendet werden, wie nötig um jeden Knoten des Speicherpools zu addressieren. In [LA05] wird diese Vorgehensweise für verknüpfte Datenstrukturen vorgestellt. Dafür muss man im Vorhinein wissen wie viele Knoten maximal auftreten werden. Wird diese Zahl überschritten,
muss der Baum neu aufgebaut werden.
Reichen beispielsweise 3 Byte um alle Knoten zu addressieren kann der
Verzweigungsgrad bei 256-Byte-Knoten von 15 auf 22 erhöht werden.
Sind auch die Schlüssel nicht 8 Byte groß können auch diese verkleinert
werden.
3.2.2
Dynamische Komprimierung mit Differenzen
Durch die Komprimierung mit zur Compilezeit definierten Werten, wird
die Flexibilität eingeschränkt. Deshalb wird eine dynamische Komprimierung mit Differenzwerten angedacht. Der jeweilige Werte berechnet
sich aus der Summe der Schlüssel auf dem Pfad zum Schlüssel. Dadurch
muss jeweils nur die Differenz zum Vater gespeichert werden.
Die benötigte Byte-Anzahl für die Schlüssel wird in jedem Knoten gesondert festlegt. Der Wert für die IDs wird weiterhin über eine Konstante
(zwischen 2 und 8) bei der Erstellung des Baumes definiert.
Die Byteanzahl für einen Schlüssel kann 3-8 Bytes betragen. Bei Schlüssel
mit nur 1 oder 2 Bytes könnte ein Überlauf dazu führen, dass eine Aufspaltung des Knotens nicht genügt.
15
KAPITEL 3. MEHRWEGBÄUME
Die Belegung eines Knotens wird, wie in Abbildung 3.3 ersichtlich, im
ersten Byte eines Knotens gespeichert und im zweiten die Byte-Anzahl
für die Schlüssel des Knotens.
size: 5 bytes: 3 22 27 32 40 50 ...
Abbildung 3.3: Aufbau eines Knotens für die dynamische Komprimierung.
Einfüge-Operation Beim Einfügen eines neuen Schlüssels muss nicht
nur darauf Rücksicht genommen werden, ob der Schlüssel noch im Blatt
Platz findet. Es muss auch überprüft werden ob der Schlüssel durch
die bestehende Byte-Anzahl dargestellt werden kann. Sind diese beiden
Voraussetzungen erfüllt, unterscheidet sich das Einfügen nicht vom unkomprimierten B-Baum.
Ist dies nicht der Fall wird das Blatt aufgespalten. Bei dem linken Blatt
kann nun überprüft werden, ob weniger Bytes ausreichen um das größte
Element darzustellen. Beim rechten wird ebenfalls das größte Element
überprüft und je nach Größe neu aufgebaut. Mehr Bytes sind nur nötig
wenn der neue Schlüssel der größte im Knoten ist. Der Median wird
an den Vater weitergereicht. Nach dem Einfügen des Medians müssen
möglicherweise die Differenzn in der Ebene darunter angepasst werden.
Lösch-Operation Beim Löschen wird der Schlüssel zunächst wie beim
B-Baum üblich entfernt. Handelt es sich um den größten Schlüssel im
Knoten (Schlüssel ganz rechts) wird zusätzlich überprüft ob die Komprimierung erhöht werden kann. Möglicherweise müssen auch wieder Differenzwerte angepasst werden.
Erwartete Performance Durch die nötigen dynamischen Berechnungen kann sich die Performance nicht gegenüber einem Baum mit
statischer Komprimierung, gleicher Pointer- und Schlüsselgröße, verbessern. Der Vorteil besteht darin, dass alle Schlüsselgrößen abgedeckt werden und trotzdem eine Komprimierung und Performancesteigerung gegenüber dem unkomprimierten Baum eintritt.
16
KAPITEL 3. MEHRWEGBÄUME
3.3
Verwandte Arbeiten
Durch das große Interesse an Hauptspeicherdatenbanken sind Indexstrukturen für diese ein aktives Forschungsgebiet. Hier werden einige
Alternativen in Form von Mehrwegbäume vorgestellt.
3.3.1
CSB+ -Baum
Im Artikel ”Making B+ -trees cache conscious in main memory” wird
eine neue Datenstruktur auf Basis von B+ -Bäumen vorgestellt.[RR00]
Wie bei B+ -Bäumen üblich, werden die Daten in den Blättern gespeichert. Jedoch werden Veränderungen vorgenommen, um den Cache besser auszunützen. Dazu werden in einem Knoten nicht unbedingt k+1
Nachfolger bei k Schlüsseln gespeichert, sondern eine variable Anzahl.
Die fehlenden Nachfolger lassen sich über Offsets zu den angegebenen
berechnen. Dazu ist es erforderlich, dass alle Geschwister, welche durch
einen Pointer in ihrer Position festgelegt werden, direkt aufeinanderfolgend im Speicher liegen. Dieser Verbund der hintereinander liegenden
Geschwister wird als node-group bezeichnet.
Besonders eignet sich diese Datenstruktur für Datenbestände, welche
sich selten ändern, da Update-Operationen teuer sind. Der Grund dafür
ist, dass bei Veränderungen der Schlüsselmenge in einem Knoten oft
Kopiervorgänge nötig sind, um alle Geschwister wieder hintereinander
im Speicher zu positionieren.
Der Geschwindigkeitsvorteil bei der Suche ergibt sich aus dem größeren
Anteil von Schlüsseln im Knoten und dadurch einem höheren Verzweigungsgrad bei gleicher Cache-Line-Size gegenüber einem B+ -Baum.
CSB+ -Baum mit einem Pointer Wird nur ein Pointer für alle Kinder gespeichert, verweist dieser bei einem Knoten mit k Schlüsseln auf
ein Speichersegment mit mindestens k+1 Knoten. Bei der Suche wird
nun im Knoten das kleinste Element das größer als der gesuchte Knoten ist ausgewählt. Je nach dessen Position berechnet sich der Offset
der zum Pointer dazugerechnet wird. Ist der Schlüssel kleiner als das
minimale Element des Knotens, entspricht dieser Offset 0.
CSB+ -Baum mit mehreren Pointer Sind mehrere Pointer pro Knoten vorgesehen, werden diese über die maximale Anzahl von Schlüsseln
gleichverteilt. Die Adresse zu den Kindknoten berechnet sich dann über
den Pointer links des kleinsten Schlüssels, größer dem Gesuchten und
17
KAPITEL 3. MEHRWEGBÄUME
dem relativen Offset zu diesem. Wie bereits erwähnt sind Update-Operationen
teuer. Durch die Verwendung von mehreren Pointern kann dies verbessert werden, weil die node-groups kleiner werden und so die zu kopierenden Bereiche kleiner sind. Die Verwendung von mehreren Pointern
verschlechtert aber die Performance, weil der Schlüsselanteil im Knoten
sinkt.
Vollständiger CSB+ -Baum Bei einem vollständigen CSB+ -Baum
werden die node-groups immer mit der maximal benötigten Größe angelegt. Das heißt für die maximale Anzahl von Geschwistern mit der
maximalen Größe. Dadurch wird das Allozieren von node-groups nicht
mehr beim Überlaufen von Knoten, sondern nur mehr beim Überlauf
von node-groups notwendig.
Performance Vollständige CSB+ -Bäume sind sowohl bei der Suche,
als auch bei Update-Operationen den B+ -Bäumen überlegen. Sie benötigen
jedoch sehr viel Speicher. CSB+ -Bäume mit einem Pointer sind sehr
schnell bei der Suche, jedoch langsam bei Update-Operationen. Solche
mit mehreren Pointern sind ausgewogener, und laut [RR00] den B+ Bäumen, bei Suche, Update-Operationen und auch beim Speicherverbrauch überlegen.
18
Kapitel 4
Evaluierung der Bäume
Die amortisierten Komplexitäten der vorgestellten Bäume sind für den
average-case dieselben. Die Ausnahme bildet hier nur der natürliche
Binärbaum. Dieser wurde jedoch nur betrachtet, da die Operationen
der anderen Datenstrukturen mit diesen eng verknüpft sind.
Für die Operationen Suchen, Einfügen und Löschen ist die Komplexität jeweils O(log(n)). Jedoch unterscheiden sich die Komplexitäten
im Detail. Diese Unterschiede werden in diesem Kapitel untersucht. Aufgrund der Ergebnisee wird entschieden, welche Datenstrukturen umgesetzt werden.
4.1
Speicherverbrauch
Alle Datenstrukturen haben eine Speicherkomplexität von O(n). Werden die relativen Speicheradressen und der Schlüssel mit jeweils 8 Bytes
angenommen, verbraucht ein Knoten eines Scapegoat-Baums insgesamt
24 Byte für die beiden Speicheradressen der Kinder und den Schlüssel.
Der Rot-Schwarz-Baum benötigt zusätzlich ein Bit für die Farbinformation und der AVL-Baum 2 Bits für den Balancewert. Diese zusätzlichen
Daten können aber, wie bereits beschrieben in anderen Variablen, welche
nicht den gesamten Wertebereich nutzen, mitgespeichert werden.
Beim B-Baum mit einem maximalen Belegungsgrad von 256 Schlüssel
reicht ein Byte, um die derzeitige Belegung im Knoten zu speichern.
Daraus ergibt sich eine Knotengröße von 1 + x · 8 + (x + 1) · 8 Byte
für einen Knoten mit maximal x Schlüsseln und dementsprechend x+1
Kindern mit jeweils 8 Byte. Durch Komprimierung lassen sich auch hier
Verbesserungen erzielen. Mögliche Vorgehensweisen wurden bereits im
Abschnit 3.2 erläutert.
19
KAPITEL 4. EVALUIERUNG DER BÄUME
4.2
Such-Operation
Die Performance der Such-Operation wird durch zwei Faktoren beeinflusst: der Anteil der Cachetreffer und die Höhe des Baums. Der AVLund B-Baum garantieren, dass der Baum absolut ausgeglichen bzw.
höchstens um den Faktor 2 unterschiedlich hoch ist, während der ScapegoatBaum abhängig vom α-Wert balanciert ist. Dadurch ergibt sich beim
AVL-Baum eine maximale Höhe von log2 (n), beim Rot-Schwarz-Baum
2 · log2 (n + 1) und beim Scapegoat-Baum log1/α (n) + 1. Beim B-Baum
hängt die Höhe vom gewählten Verzweigungsgrad ab und beläuft sich
auf logk ( n+1
2 ). Bei den Cache-Treffern könnte der B-Baum Vorteile haben, da mehrere Schlüssel in einem Knoten liegen und so, je nach Größe,
eine gewisse Anzahl von Schlüsseln in derselben Cache-Line sind.
4.3
Einfügen und Löschen
Der AVL-Baum und der Rot-Schwarz-Baum verfolgen hier ähnliche Verfahren: die Balanceunterschiede werden mittels Rotationen ausgeglichen.
Der Unterschied besteht darin, dass auf verschiedene Weise festgelegt
wird, wann und wo eine Rotation nötig ist. Beim AVL-Baum kommt
es zu maximal log2 (n) Rotationen und beim Rot-Schwarz-Baum beim
Einfügen zu maximal 2 und beim Löschen zu maximal 3 Rotationen.
Der Scapegoat-Baum hingegen baut Teile des Baumes komplett neu auf.
Die Implementierung ist zwar vergleichsweise einfach, jedoch für große
Schlüsselmengen, wegen der größeren Höhe und des möglichen Neuaufbauens weniger geeignet.
Beim B-Baum werden möglicherweise Knoten zusammengefügt bzw. aufgespalten. Dabei müssen abhängig von der Größe eines Knotens viele
Daten kopiert werden. Um eine Häufung dieses Vorganges zu senken,
sollte der Verzweigungsgrad möglichst hoch gehalten werden. Da der
Speicherzugriff auch beim Arbeitsspeicher teuer ist, könnte Komprimierung als Möglichkeit zur Performancesteigerung dienen.
20
Kapitel 5
Benchmarks
Um die Performance der verschiedenen Datenstrukturen zu vergleichen,
wurde eine einfache Benchmarksuite erstellt. Mit dieser können die jeweils gewünschten Datenstrukturen anhand einer angegeben Operation
und eines Testsamples getestet werden. Außerdem ist es möglich die
Datenstrukturen initial mit Schlüsseln zu füllen.
Vor der Messung wird der Cache angewärmt. Anschließend werden mehrere Durchläufe ausgeführt und der Durchschnitt berechnet.
Abbildung 5.1: Beispielhafte Gnuplot-Ausgabe eines Benchmarks.
21
KAPITEL 5. BENCHMARKS
Abbildung 5.1 zeigt die Ausgabe eines Benchmarkdurchlaufs. Auf der
x-Achse wird die Anzahl der verarbeiteten Knoten und auf der y-Achse
die verstrichene Zeit in Millisekunden aufgetragen.
5.1
Testsystem
Die Benchmarks wurden auf einem Intel Core i5 Quad-Core System mit
2,66GHz und 6GB Ram durchgeführt. Die CPU verfügt über 64KB L1,
256KB L2 Cache pro Core und 8MB geteilt benützten L3 Cache. Die
Cache Line Size beträgt 64 Byte.
5.2
5.2.1
Untersuchte Datenstrukturen
Natürlicher binärer Suchbaum
Die Implementierung orientiert sich am Pseudo-Code der in [CLRS09]
angegeben wird. Es wurden aber Änderungen vorgenommen, um relative
Adressen für die Knotenaddressierung zu verwenden.
Bei den weiteren Benchmarks wird auf die Darstellung des natürlichen
Binärsuchbaums verzichtet. Die Abbildung 5.2 zeigt das Einfügen von
10000 Schlüsseln. Es ist ersichtlich, dass der Baum degeneriert und so
die Laufzeit sehr stark ansteigt.
5.2.2
Rot-Schwarz-Bäume
Auch diese Impelementierung basiert wiederum auf dem Pseudo-Code
in [CLRS09]. Dieser entspricht dem des natürlichen binären Suchbaums,
erweitert um das Setzen der Farbe eines Knoten und der Funktionalität
zum Balancieren.
Komprimierung der Farbinformation
Wie bereits im Abschnitt 2.2.1 angedacht, wurde die Komprimierung der
Farbinformation implementiert. Die verwendete Implementierung wird
in A.1.1 gezeigt.
Bei den Benchmarks zum Vergleich von Rot-Schwarz-Bäumen wurden
jeweils 1 Million Knoten getestet. Das heißt beim Einfügen wurden diese
Schlüssel in den leeren Baum eingefügt und bei der Suche zuerst in der
22
KAPITEL 5. BENCHMARKS
Abbildung 5.2: Einfügen in aufsteigender Reihenfolge in einem
natürlichen Binärbaum.
angegebenen Reihenfolge eingefügt und dann wie angegeben gesucht. Es
wurden jeweils alle eingefügten Schlüssel gesucht.
Beim Einfügen ist die Performance durch das Decodieren des Elternknotens schlechter. Man sieht jedoch, dass dieser Unterschied beim Einfügen
in zufälliger Reihenfolge, wie in Abbildung 5.5 ersichtlich, geringen ausfällt,
da weniger Decodiervorgänge vorgenommen werden müssen, als beim
Einfügen in aufsteigender Reihenfolge (Abbildung 5.6).
Bei der Suche kann die kleinere Größe der Knoten Vorteile haben. Da
bei der Suche kein Decodieren der ID des Vaters nötig ist, lässt sich das
nachvollziehen.
In den folgenden Benchmarks wird nur mehr der Rot-Schwarz-Baum mit
aktivierter Kompression verwendet.
23
KAPITEL 5. BENCHMARKS
Abbildung 5.3: Suchen in aufsteigender Reihenfolge. Schlüssel wurden
aufsteigend eingefügt.
Abbildung 5.4: Suchen in zufälliger Reihenfolge. Schlüssel wurden in
aufsteigender Reihenfolge eingefügt.
24
KAPITEL 5. BENCHMARKS
Abbildung 5.5: Einfügen in zufälliger Reihenfolge.
Abbildung 5.6: Einfügen in aufsteigender Reihenfolge.
25
KAPITEL 5. BENCHMARKS
Prefetching
Beim Binärbaum besteht nur die Möglichkeit, dass entweder der linke
oder der rechte Teilbaum besucht werden muss. Es wurde versucht vor
dem Testen, ob die Operation im linken oder rechten Teilbaum fortgesetzt wird, bereits beide Knoten in den Cache zu laden.
Prefetching mit der GCC-Funktion builtin prefetch brachte keine Performancevorteile. Der Grund dafür dürfte sein, dass die Zeitspanne zwischen dem Funktionsaufruf und dem Speicherzugriff zu kurz ist und so
die benötigten Daten noch nicht im Cache liegen. Außerdem müssen die
absoluten Adressen beider Nachfolger berechnet werden, obwohl man
dann nur eine wirklich benötigt.
5.2.3
B-Baum
Verschiedene Verzweigungsgrade
Die Implementierung ermöglicht es die Größe eines Knotens in Byte im
Header der Datenstruktur festzulegen. Besonders eignen sich Vielfache
der Cache-Line-Size des jeweiligen Systems. Bei aktuellen Prozessoren
sind dies meistens 64 Byte. Es stellte sich eine Größe von 512 Byte als am
schnellsten heraus. Dies entspricht bei 8-Byte-Schlüsseln und Adressen
einem Verzweigungsgrad von 32. Bei der Suche in einem Knoten handelt
es sich um eine lineare Suche. Möglicherweise würde eine Binärsuche in
Kombination mit größeren Knoten Performancevorteile bringen.
Dieses Ergebnis deckt sich auch mit den Resultaten des Artikels [HP03].
Dort wurden zwar Knotengrößen für B+ -Bäume untersucht. Es darf aber
angenommen werden, dass diese Ergebnisse auch für B-Bäume gelten.
Prefetching des gesamten Knotens
Da die Cache-Line-Size gewöhnlich kleiner als die Größe des gesamten Knotens ist, macht Prefetching Sinn. Beispielsweise bei einer Knotengröße von 256 Bytes und einer Cache-Line von 64 Byte wird die
builtin prefetch Funktion viermal jeweils versetzt um 64 Bytes aufgerufen. Benchmarks zeigen, dass Geschwindigkeitssteigerungen von 10%
möglich sind.
26
KAPITEL 5. BENCHMARKS
Komprimierung
5.3
Ausgwählte Resultate
Abbildung 5.7: Einfügen von 10 Millionen aufsteigenden Schlüsseln.
Abbildung 5.8: Einfügen von 10 Millionen Schlüsseln in zufälliger Reihenfolge.
27
KAPITEL 5. BENCHMARKS
Abbildung 5.7 und 5.8 zeigen das Einfügen in aufsteigender bzw. zufälliger
Reihenfolge. Man sieht, dass beim zufälligen Einfügen die Kurve bei
den B-Bäumen gleichmäßiger verläuft. Der Grund dafür ist, dass der
Füllgrad höher ist und dadurch die Datenstruktur eine Ebene niedriger
ist. Der Knick deutet darauf hin, dass hier ein Wachsen in der Höhe
stattgefunden hat. Die Performance des B-Baums ist mehr als 100%
schneller als die des Rot-Schwarz-Baumes.
Abbildung 5.9: Suchen von 1 Million Schlüsseln in zufälliger Reihenfolge.
Schlüssel wurden auch in zufälliger Reihenfolge eingefügt.
Die Abbildungen 5.9 und 5.10 bilden das Suchen zufälliger bzw. aufsteigender Reihenfolge ab. Es werden jeweils 1 Million Schlüssel gesucht.
Die Datenstruktur werden im Vorhinein mit 1 Million Schlüssel, jeweils
in zufälliger Reihenfolge befüllt.
Es zeigt sich wieder ein Performancevorteil von mehr als 100% bei den BBäumen gegenüber dem Rot-Schwarz-Baum. Der komprimierte B-Baum
hat in beiden Fällen Performancevorteile gegenüber der unkomprimierten Variante
28
KAPITEL 5. BENCHMARKS
Abbildung 5.10: Suchen von 1 Million Schlüsseln in aufsteigender Reihenfolge. Schlüssel wurden in aufsteigender Reihenfolge eingefügt.
29
Kapitel 6
Zusammenfassung und
Ausblick
Diese Arbeit evaluiert verschiedene Indexstrukturen sowohl theoretisch,
als auch durch Benchmarks. Durch die Benchmarks wurde aufgezeigt,
dass die Cache-Optimierung, auch durch Kompression, großen Einfluss
auf die Performance hat. Aufwendige Algorithmen mit mehr Instruktionen fallen durch die Memory-Wall, also den kleinen Fortschritt bei
Speicherlatenzen gegenüber den raschen Verbesserungen der Prozessoren, kaum ins Gewicht. Der komprimierte B-Baum stellte sich als schnellste Datenstruktur heraus. Zurückzuführen ist das auf die gute CacheAusnützung, welche sich bei der Suche positiv auswirkt. Durch die Komprimierung erhöht sich aber auch der Verzweigungsgrad und die Häufigkeit
von Restrukturierungen bei Update-Operationen kann gesenkt werden.
Die verwendete Komprimierung basiert darauf, dass anstatt von absoluten Werten nur Differenzwerte gespeichert werden. Der absolute Wert
berechnet sich über die Summe der relativen Werte auf dem Pfad zum
jeweiligen Schlüssel.
Man könnte den Kompressionsgrad noch weiter erhöhen indem man
nicht nur relative Werte im Bezug zur Wurzel verwendet, sondern dieses Verfahren nur für einen Schlüssel eines Knotens anwendet und die
restlichen Schlüssel wiederum relativ zu diesem kodiert.
Gerade bei Datensätzen mit kleinen Differenzen zwischen den Schlüsseln
kann sich so ein sehr hoher Kompressionsgrad ergeben.
30
Appendix
A.1
A.1.1
Implementierungsdetails
Komprimierung der Farbinformation des Rot-SchwarzBaums
Für die Farbe wird ein Bit benötigt. Dieses wird im niederstwertigen Bit
des Pointers zum Vater gespeichert. Die Vorgehensweise mittels Bitoperationen wird in Listing 1 gezeigt. Bei color handelt es sich um einen
enum mit den Werten 0 für rot und 1 für schwarz.
Listing 1: Mitspeichern der Farbinformation in einem 64-Bit Pointer
c o l o r g e t c o l o r ( struct p a r e n t n o d e ∗ x ) {
return x−>p a r e n t & 1 ;
}
s i z e t g e t p a r e n t i d ( struct p a r e n t n o d e ∗ x ) {
return ( x−>p a r e n t >> 1 ;
}
void s e t p a r e n t i d ( struct p a r e n t n o d e ∗ x , s i z e t i d ) {
x−>p a r e n t = ( x−>p a r e n t & 1 ) | ( i d << 1 ) ;
}
void s e t c o l o r ( struct p a r e n t n o d e ∗ x , c o l o r c o l o r ) {
x−>p a r e n t = ( x−>p a r e n t & ˜ 1 ) | c o l o r ;
}
A.2
Speicherverwaltung
Die Datenstrukturen verwenden eine eigene Speicherverwaltung in Form
einer Freispeicherliste. Das hat den Vorteil, dass die Allozierung von
Speicher weniger Einfluss auf die Laufzeit der Datenstrukturen hat.
Dadurch werden die Ergebnisse der Benchmarks reproduzierbarer. Ein
weiterer Vorteil ist, dass die gesamte Datenstruktur in einem einzigen
Speicherblock liegt und so leicht in eine Datei gespeichert werden kann.
31
APPENDIX A.2. SPEICHERVERWALTUNG
Diese Datei kann auch mittels mmap eingebunden werden, wodurch die
persistente Speicherung ermöglicht wird. Eine weitere Voraussetzung,
dass dies funktioniert ist die Verwendung von relativen Adressen. Außerdem kann eine Speicherverwaltungsinstanz mehrere Datenstrukturen
beinhalten, jedoch müssen diese implementierungsbedingt vom selben
Typ sein.
A.2.1
Anlegen des Speichers und von Datenstrukturen
Speicher wird mit der memory malloc-Funktion angefordert. Beim Funktionsaufruf wird angegeben wie viele Schlüssel mindestens im initial angelegten Speicher Platz finden sollen. Die so erzeugte memory-Struktur
ist der Rückgabewert der memory malloc-Funkion und muss bei den
Operationen auf der Datenstruktur referenziert werden. Sollte der Speicher zu klein sein, wird dieser automatisch vergrößert. Die gewünschte
Reallozierungsstrategie kann durch ein Präprozessormakro frei festgelegt
werden.
Die weiteren Funktionen, welche die Speicherverwaltung zur Verfügung
stellt, sind im memorymanagement.h-Header definiert:
s i z e t memory get ( struct memory∗∗ mem)
b o o l m e m or y f r ee ( struct memory∗∗ mem, s i z e t s e g i d )
void ∗ memory dref ( struct memory∗∗ mem, s i z e t i d )
struct d a t a s t r u c t u r e ∗ m e m o r y a d d d a t a s t r u c t u r e ( struct memory∗∗ mem)
b o o l m e m o r y d e s t r o y d a t a s t r u c t u r e ( struct memory∗∗ mem, s i z e t i d )
b o o l memory save ( struct memory∗∗ mem)
b o o l memory saveas ( struct memory∗∗ mem, char∗ path )
b o o l m e m o r y s e t p a t h ( struct memory∗∗ mem ptr ptr , char∗ path )
struct memory∗∗ memory load ( char∗ path , enum memory mode mode )
struct d a t a s t r u c t u r e ∗ m e m o r y l o a d d a t a s t r u c t u r e ( struct memory∗∗ mem, s i z e t i d )
struct d a t a s t r u c t u r e ∗ m e m o r y l o a d d a t a s t r u c t u r e s ( struct memory∗∗ mem)
Will man eine Datenstruktur im Speicher anlegen, geschieht dies mit der
memory add datastructure-Funktion. Diese Funktion liefert dann einen
Pointer auf die datastructure-Struktur zurück. Um eine Datenstruktur
zu löschen wird die memory destroy datastructure-Funktion benützt.
Die Funktionen memory get und memory free fordern ein Speichersegment der beim Erstellen des Speichers definierten Größe an bzw. geben
dieses frei. Da die Speicherverwaltung mit relativen Adressen arbeitet,
werden diese als IDs (vom Typ size t) bezeichnet. Um solche IDs zu
absoluten Adressen aufzulösen steht die memory dref-Funktion bereit.
Will man eine Datenstruktur aus dem Speicher löschen, macht man dies
mit der memory destroy datastructure-Funktion.
Die verbleibenden Funktionen werden zum Speichern und laden einer
Datenstruktur bzw. des gesamten Speichersegmentes benützt. Die Funktionen memory save bzw. memory save as veranlassen das Speichern des
32
APPENDIX A.2. SPEICHERVERWALTUNG
Segments in eine Datei. Dabei setzt die memory save-Funktion voraus,
dass bereits eine Datei mit memory set path gesetzt worden ist.
Um eine so gespeichterte Datei wieder zu laden steht die memory loadFunktion zur Verfügung. Der Parameter mode kann entweder in memory
oder mapped sein. Ersteres bedeutet, dass die Datei komplett in den
Arbeitsspeicher geladen wird. Im Gegensatz dazu wird die Datei bei
mapped mittels mmap eingebunden. Dadurch werden Veränderungen
an den Datenstrukturen automatisch persistent in die Datei geschrieben, während bei in memory dieser Schritt explizit erfolgen muss.
Die Abbildung 1 zeigt schematisch den Aufbau des Speichers. Die memoryStruktur wird vom Bereich für die Speicherung der einzelnen Baumknoten gefolgt. Am Ende des Speichers befinden sich die datastructureStrukturen, welche Metadaten über die verschiedenen Datenstrukturen
enthalten.
Abbildung 1: Der Aufbau der Speicherverwaltung mit einer bzw. zwei
Datenstrukturen.
33
Literaturverzeichnis
[And93]
A. Andersson: Balanced Search Trees Made Simple, Proceedings of the Third Workshop on Algorithms and Data Structures, WADS ’93, Springer-Verlag, London, UK, pages 60–71.
[AT07]
A. Andersson and M. Thorup: Dynamic ordered sets with exponential search trees, J. ACM, volume 54.
[Bay72]
R. Bayer: Symmetric binary B-Trees: Data structure and
maintenance algorithms, Acta Informatica, volume 1, (1972),
pages 290–306, 10.1007/BF00289509.
[CLRS09] T. H. Cormen, C. E. Leiserson, R. L. Rivest and C. Stein:
Introduction to Algorithms, Third Edition, The MIT Press,
3rd edition, 2009.
[GR93]
I. Galperin and R. L. Rivest: Scapegoat trees, Proceedings of
the fourth annual ACM-SIAM Symposium on Discrete algorithms, SODA ’93, Society for Industrial and Applied Mathematics, Philadelphia, PA, USA, pages 165–174.
[GS78]
L. J. Guibas and R. Sedgewick: A dichromatic framework
for balanced trees, Foundations of Computer Science, Annual
IEEE Symposium on, volume 0, (1978), pages 8–21.
[HP03]
R. A. Hankins and J. M. Patel: Effect of node size on the performance of cache-conscious B+-trees, SIGMETRICS Perform. Eval. Rev., volume 31(1), (2003), pages 283–294.
[LA05]
C. Lattner and V. S. Adve: Transparent pointer compression
for linked data structures, Proceedings of the 2005 workshop
on Memory system performance, MSP ’05, ACM, New York,
NY, USA, pages 24–35.
[LC86]
T. J. Lehman and M. J. Carey: A Study of Index Structures for Main Memory Database Management Systems, Proceedings of the 12th International Conference on Very Large
35
APPENDIX LITERATURVERZEICHNIS
Data Bases, VLDB ’86, Morgan Kaufmann Publishers Inc.,
San Francisco, CA, USA, pages 294–303.
[LNT00]
H. Lu, Y. Y. Ng and Z. Tian: T-Tree or B-Tree: Main Memory Database Index Structure Revisited, Proceedings of the
Australasian Database Conference, ADC ’00, IEEE Computer
Society, Washington, DC, USA, pages 65–.
[LSLC07] I.-h. Lee, J. Shim, S.-g. Lee and J. Chun: CST-trees: cache
sensitive t-trees, Proceedings of the 12th international conference on Database systems for advanced applications, DASFAA’07, Springer-Verlag, Berlin, Heidelberg, pages 398–409.
[OW02]
T. Ottmann and P. Widmayer: Algorithmen und Datenstrukturen, Spektrum Akademischer Verlag, 2002.
[RR99]
J. Rao and K. A. Ross: Cache Conscious Indexing for
Decision-Support in Main Memory, M. P. Atkinson, M. E.
Orlowska, P. Valduriez, S. B. Zdonik and M. L. Brodie (eds.),
VLDB’99, Proceedings of 25th International Conference on
Very Large Data Bases, September 7-10, 1999, Edinburgh,
Scotland, UK, Morgan Kaufmann, pages 78–89.
[RR00]
J. Rao and K. A. Ross: Making B+- trees cache conscious
in main memory, SIGMOD Rec., volume 29, (2000), pages
475–486.
[Sed08]
Sedgewick: Left-Leaning Red-Black Trees, 2008, URL http:
//www.cs.princeton.edu/~rs/talks/LLRB/LLRB.pdf.
[WM95]
W. A. Wulf and S. A. McKee: Hitting the memory wall: implications of the obvious, SIGARCH Comput. Archit. News,
volume 23, (1995), pages 20–24.
36
Herunterladen