Delta-Kodierung im B-Baum - Institut für Informatik

Werbung
Leopold-Franzens-Universität Innsbruck
Institut für Informatik
Datenbanken und Informationssysteme
Delta-Kodierung im B-Baum
Bachelor-Arbeit
von Thomas Meindl
betreut von
Dipl.-Ing. Robert Binna
und
Prof. Dr. Günther Specht
Innsbruck, 26. Oktober 2013
Zusammenfassung
Effiziente B-Baum Implementierungen erfordern einen hohen Verzweigungsgrad und sind hinsichtlich der Eigenschaften des CPU-Caches optimiert. Diese Arbeit beschreibt, wie sich auf Grundlage der Delta-Kodierung eine Datenkompression für die B-Baum-Datenstruktur realisieren
lässt. Die dafür notwendigen Modifikationen der Algorithmen und daraus resultierenden Auswirkungen bezüglich des Performanceverhaltens
werden erörtert. Anschließend erfolgt eine Darstellung verschieden strukturierter Knoten mit ihren spezifischen Eigenschaften. In der Evaluierung zeigt sich das Verhalten der unterschiedlichen inneren Strukturen
in Verbindung mit dem in dieser Arbeit beschriebenen Delta-kodierten
B-Baum. Diese B-Baum Varianten werden mit dem konventionellen BBaum und den Rot-Schwarz Suchbäumen des Java TreeSets und des
C++ STL-Sets verglichen. Dabei ist ihr Speicherbedarf geringer und sie
erreichen annähernd die Leistungsfähigkeit des TreeSets.
Abstract
Efficient B-tree implementations provide a high fan-out rate and are
optimized in respect to the properties of the CPU-cache. On the basis
of the so called delta-encoding, this thesis presents a data compression
scheme for B-trees. It describes the modifications that are needed for
the algorithms and the effects in regard to the performance of the proposed data-structure. Additionally, this paper introduces different node
structures and discusses their properties. An evaluation compares the
behavior of the proposed inner structures for the presented B-tree type
and compares it with the conventional B-Tree and the Red-Black trees
provided by the TreeSet of Java and STL Set of C++. This shows that
delta-encoded B-trees consume less memory and are able to provide almost the same performance as the Java TreeSet.
Inhaltsverzeichnis
1 Einleitung
1
2 Relevante Arbeiten
3
2.1
B-Baum Datenstrukturen . . . . . . . . . . . . . . . . . .
3
2.2
Arbeiten mit Schwerpunkt Performance . . . . . . . . . .
5
3 Delta-Kodierung im B-Baum
3.1
3.2
7
Der Delta-kodierte B-Baum . . . . . . . . . . . . . . . . .
7
3.1.1
Grundlagen und Definition . . . . . . . . . . . . .
7
3.1.2
Die Suche im Baum . . . . . . . . . . . . . . . . .
9
3.1.3
Einfügen von Schlüsseln . . . . . . . . . . . . . . . 11
3.1.4
Umverteilung von Schlüsseln
3.1.5
Löschen von Schlüsseln . . . . . . . . . . . . . . . . 15
3.1.6
Zusammenfassung der Operationen . . . . . . . . . 17
. . . . . . . . . . . . 13
Die Knotenarchitektur des B-Baumes . . . . . . . . . . . . 19
4 Evaluierung
23
4.1
Testumgebung
4.2
Methodik der Benchmarks . . . . . . . . . . . . . . . . . . 24
4.3
Der Speicherverbrauch . . . . . . . . . . . . . . . . . . . . 24
4.4
. . . . . . . . . . . . . . . . . . . . . . . . 23
4.3.1
Verbrauch beim Aufbau . . . . . . . . . . . . . . . 25
4.3.2
Verbrauch nach dem Aufbau . . . . . . . . . . . . 26
Benchmarks über Einfügeoperationen . . . . . . . . . . . . 28
4.4.1
Menge aus direkt aufeinanderfolgenden Schlüsseln . 28
4.4.2
Randomisierte Schlüsselmengen . . . . . . . . . . . 28
4.4.3
Rein zufällige Schlüsselmenge . . . . . . . . . . . . 30
4.5
Benchmarks über Suchvorgänge . . . . . . . . . . . . . . . 30
4.6
Zusammenfassung der Ergebnisse . . . . . . . . . . . . . . 33
III
INHALTSVERZEICHNIS
5 Zusammenfassung und Ausblick
35
Appendix
37
A.1 Compilieren des HotSpot-Disassemblers . . . . . . . . . . 37
A.2 Löschen nach Cormen et al. . . . . . . . . . . . . . . . . . 38
A.3 Der Delta-kodierte B+ Baum . . . . . . . . . . . . . . . . 39
A.3.1 Einfügen von Schlüsseln . . . . . . . . . . . . . . . 39
A.3.2 Löschen von Schlüsseln . . . . . . . . . . . . . . . . 40
Literaturverzeichnis
IV
42
Kapitel 1
Einleitung
Die Leistung von DBMS wird durch die zugrundeliegenden Indexstrukturen bestimmt. Die Ausführungsgeschwindigkeit dieser Schicht lässt sich
optimieren, indem Datenstrukturen auf die verwendete Hardware abgestimmt werden. So erlauben B-Bäume eine effiziente Verwaltung von
Datensätzen auf Festplatten, da ihr innerer Aufbau bestmöglich an die
Blockgröße der Massenspeicher angepasst ist.
Durch die stete Zunahme der Hauptspeichergröße können Datenbanken
wortwörtlich „In-Memory“ gehalten werden. Aufgrund der Anpassung
an die Seitengröße des Hauptspeichers dienen B-Bäume hier ebenso als
effiziente Hauptspeicherdatenstruktur. Da sich die Geschwindigkeit von
Mikroprozessor und Hauptspeicher in Größenordnungen unterscheidet,
wird das Laufzeitverhalten von speicherintensiven Anwendungen ausschließlich durch eine Cache-Hierarchie verbessert. Für eine hohe Performance muss daher die innere Struktur des B-Baumes mit der Größe
der Cache-Line in Übereinstimmung gebracht werden.
Mithilfe von Kompressionstechniken lassen sich Daten in einem Knoten
platzsparender unterbringen. Speziell bei 64-Bit Architekturen ist es vorteilhaft, den Platzbedarf für Zeiger einzuschränken, damit dadurch eine
bestmögliche Nutzung der Cache-Zeile möglich wird.
Die eben beschriebene Problematik bildet die Motivationsgrundlage der
vorliegenden Arbeit. Es wird hier ein Cache-sensitiver B-Baum realisiert,
der mit Hilfe verschiedener Verfahren Schlüssel und Zeiger mit reduziertem Speicherbedarf in den Knoten ablegen kann. Er ist damit dem
klassischen B-Baum in Hinsicht auf den Speicherverbrauch überlegen.
Die Gliederung der Arbeit gestaltet sich wie folgt: In Kapitel 2 werden
relevante Arbeiten zu herkömmlichen und Cache-sensitiven B und B+
Bäumen vorgestellt.
Kapitel 3 zeigt die Grundsätze der Datenreduktion durch kürzere Schlüs-
1
KAPITEL 1. EINLEITUNG
sel für den hier vorgestellten B-Baum und erörtert die Änderungen an
den Algorithmen. Des Weiteren werden verschiedene inneren Strukturen
für Knoten besprochen.
Kapitel 4 behandelt die Evaluierung der Datenstrukturen und diskutiert
Unterschiede zu den Standardlösungen von Java und C++.
Die Arbeit schließt mit einer Zusammenfassung und einem Ausblick in
Kapitel 5.
2
Kapitel 2
Relevante Arbeiten
2.1
B-Baum Datenstrukturen
Diese Sektion stellt relevante Arbeiten zu B-Baum Datenstrukturen vor.
Obwohl einige davon ausschließlich B+ Bäume behandeln, lassen sich
die Ergebnisse auf B-Bäume übertragen. Beide Strukturen werden in
den folgenden Abschnitten synonym verwendet.
B-Bäume werden in Organization and Maintenance of Large Ordered
Indices von Rudolf Bayer und Edward M. McCreight [BM70] erstmals für
die Organisation von Daten vorgeschlagen. Eine umfassende Übersicht
von B, B*, B+ und Präfix-B-Bäumen findet sich in The Ubiquitous BTree von Douglas Comer [Com79].
Im Artikel Making B+-Trees Cache Conscious in Main Memory stellen
Rao und Ross [RR00] mit dem cache sensitive B+ tree eine für den Cache
optimierte Variante vor. Die Autoren vergleichen die Leistung mit von
ihnen selbst in [RR99] vorgeschlagenen Cache Sensitive Search Trees.
CSS sind auf Arrays basierende binäre Suchbäume, die lediglich Schlüssel, aber keine Zeiger speichern. Gesuchte Schlüssel lassen sich durch
arithmetische Berechnungen finden. Sie unterstützen keine inkrementellen Updates und müssen in Folge einer Änderung neu aufgebaut werden. Dieser aufwändigen Operation steht ein Geschwindigkeitsvorteil bei
Suchvorgängen gegenüber.
Um B+ Bäume Cache-sensitiv zu gestalten, wird ein ähnliches Vorgehen
zusammen mit dieser sogenannten pointer elimination Technik vorgestellt: Ein Knoten enthält einen einzigen Zeiger auf eine Gruppe von
weiteren Knoten. Über diesen lässt sich zusammen mit der Schlüsselposition die Adresse des Folgeknotens berechnen. Der Trade-Off für die
eingesparten Zeiger ist ein wesentlich komplexerer und in Hinsicht auf
die Speicherallokierung aufwändigerer Split. Als Vereinfachung werden
3
KAPITEL 2. RELEVANTE ARBEITEN
Strategien zur Segmentierung von Knotengruppen und vorzeitiger Speicherreservierung präsentiert.
Der Cache-sensitive B+ Baum verwendet zusätzlich eine lazy-deletion.
Dabei werden Verschmelzungen von Knoten solange wie möglich verzögert; es gibt beim Entfernen keinen Unterlauf, stattdessen wird der
Knoten erst gelöscht, wenn er vollständig leer ist.
In Improving Index Performance through Prefetching schlagen Chen et
al. [CGM01] aufbauend auf der Arbeit von Rao und Ross vor, Knoten
größer als eine Cache-Line zu definieren. Für Sekundärspeichermedien
konzipierte Bäume können analog dazu größere Knotenkapazitäten als
die dort eingesetzte „natürliche“ Blockgröße aufweisen.
Des Weiteren beschreibt die Arbeit Auswirkungen der in den Mikroprozessoren realisierten Prefetching-Mechanismen. So lässt sich ein in zwei
Hälften segmentierter Knoten durch spezielle Befehle parallel in den Cache laden. Cache-Misses können damit zwar nicht verhindert, aber besser
verdeckt werden und verbessern deshalb die Gesamtperformance. Um eine höhere Lokalität der Schlüssel zu erreichen und sie damit für die binäre
Suche zu optimieren, werden sie zu einem eigenen Bereich zusammengezogen; zugehörigen Zeiger sind dementsprechend in einem unmittelbar
darauf folgenden Abschnitt gespeichert.
Im Speziellen zeigt die Arbeit, wie sich Blattknoten von B+ Bäumen für
das Prefetching optimieren lassen. Um Bereichsabfragen zu beschleunigen verwenden die Autoren ein externes Array bestehend aus Zeigern,
die auf Blätter verweisen. Noch während die Daten in einen Puffer geladen werden, kann damit das Prefetching für das nächste Blatt angestoßen werden. Das Array bedeutet einen zusätzlichen Verwaltungsaufwand
beim Einfügen und Löschen von Blattknoten, welcher mit verschiedenen
Optimierungen gering gehalten wird. Laut Chen et al. soll der prefetch
B+ tree genannte Baum schnellere Index-Updates und Suchvorgänge ermöglichen, als die ausschließlich auf den Cache-optimierte B+ Variante
von Rao und Ross.
Das durch die Traversierung vom Eltern- zum Kindknoten entstehende
pointer-chasing Problem wird von Chen et al. kurz diskutiert: Es ist für
einen B-Baum im Allgemeinen nicht möglich Kindknoten sinnvoll auf
Verdacht zu laden, denn erst durch die Suche im Knoten wird festgestellt welcher Bereich als nächstes von Bedeutung ist. Um diesen Effekt
zu minimieren und den Baum flacher gestalten zu können, wird eine
Kompression für Schlüssel vorgeschlagen (aber nicht näher ausgeführt).
Die Arbeit Cache Conscious Trees on Modern Microprocessors von Lee
et al. [LLSL10] vergleicht das Performanceverhalten von T und B+ Bäumen und ihren Cache-sensitiven Varianten CST und CSB+ auf modernen Mehrkernprozessoren. Bei dieser Art von Mikroprozessor kommt ein
4
KAPITEL 2. RELEVANTE ARBEITEN
weiteres von den Kernen geteiltes Cache-Level hinzu. Laut den Autoren
sind die Cache-optimierten Baumvarianten erwartungsgemäß schneller.
Darüber hinaus würden CST-Bäume bei der Suche bessere Performancewerte als CSB+, B+ oder T-Bäume erreichen.
In Main-Memory Index Structures with Fixed-Size Partial Keys verfasst von Bohannon et al. [BMR01] werden Cache-Misses als Ursache
für Performance-Einbrüche, die durch die Traversierung von MehrwegeSuchbäumen entstehen, identifiziert. Laut den Autoren ist es ineffizient,
Schlüssel bereits im Index des Baumes zu speichern. Hierbei beziehen sie
sich auf T-Bäume nach Lehman et al. [LC86] und Präfix-B-Bäume nach
Bayer [BU77]. Mit dem pkT- und pkB-Baum (i.e. partial keys) werden
zwei optimierte Varianten vorgestellt, welche partielle Schlüssel für den
Index verwenden.
Basierend auf der Idee des Präfix-B-Baumes diskutiert die Arbeit eine Methode, mit der sich der Index bei B* und T-Bäumen reduzieren
lässt. Dabei können Suchschlüssel eine variable Länge besitzen. In den
inneren Knoten werden ausschließlich Teile von Schlüsseln abgelegt, mit
denen das Auffinden des richtigen Blattknotens ermöglicht wird. IndexSchlüssel mit einer festen Länge werden Bit für Bit betrachtet. Die erste
abweichende Stelle gibt den Ausschlag für eine unterschiedliche Verzweigung. Mit diesem Vorgehen benötigt der Index weniger Speicher als der
des entsprechenden T oder B+ Baumes. Darüber hinaus müssen Vergleiche mit dem Suchschlüssel lediglich für den gerade betrachteten Teilschlüssel durchgeführt werden.
Der Gebrauch von Schlüsselfragmenten als Separatoren beschränkt den
Suchvorgang in den Knoten auf eine lineare Suche. Laut Bohannon et
al. lassen sich mit diesem Vorgehen Cache-Misses reduzieren. Als zusätzliche Beschränkung wird der eigentliche Schlüssel nicht direkt im Baum
gespeichert, sondern ein auf ihn verweisender Zeiger. Nach der Suchtraversierung ist damit immer noch eine Dereferenzierung (und damit ein
zusätzlicher Miss) notwendig, um die eigentlichen Daten zu erhalten.
2.2
Arbeiten mit Schwerpunkt Performance
Die folgenden Arbeiten beschreiben Einflussfaktoren bezüglich der Performance von Datenbanksystemen.
Ailamaki et al. [ADHW99] untersuchen in DBMSs On A Modern Processor: Where Does Time Go? Ursachen von Geschwindigkeitseinbrüchen
bei DBMS. Laut den Autoren wirken sich Level 1 Instruction Stalls und
Level 2 Data Stalls am negativsten auf die Performance aus. Die Rate
an Level 1 Instruction Misses kann durch zu große Knoten in die Höhe
getrieben werden; belegen die Daten zu viel Platz im Cache, kommt es
5
KAPITEL 2. RELEVANTE ARBEITEN
zu Verdrängungen. Als weitere Gründe für schlechte Performance, werden die Organisation des Caches, die erhöhte Anzahl von Zugriffen auf
Speicherseiten oder der Kontextwechsel des Betriebssystems genannt.
Eine genaue Analyse für die Ursachen einer schlechten Performance bei
Cache-sensitiven B+ Bäumen bietet Effect of Node Size on the Performance of Cache-Conscious B+ trees von Hankins und Patel [HP03].
Es werden vier grundlegende Faktoren analytisch und experimentell identifiziert, auf die es bei der Implementierung zu achten gilt. Sie umfassen
die Punkte Level 2 Data Cache Misses, Anzahl von Instruktionen, Branch
Prediction und Translation Lookaside Buffer.
Level 2 Data Cache Misses werden durch die binäre Suche in Zusammenspiel mit der Knotengröße erzeugt. Die Aspekte Anzahl der Instruktionen
und Branch Prediction beziehen sich auf den gesamten Suchalgorithmus,
welcher sowohl den Code für die Suche im Knoten als auch für die Traversierung des Baumes beinhaltet. Der letzte Faktor untersucht den Einfluss
des TLB-Caches auf die beim B-Baum unvermeidlichen Seitenfehler bei
der Verwaltung von virtuellem Speicher.
Die Autoren kommen unter Berücksichtigung der erwähnten Umstände
zu dem Schluss, dass eine Cache-Line nicht die optimale Größe eines
Knotens darstellt. Um negative Effekte zu minimieren, kann die Kapazität der Knoten diese weitaus übersteigen. Für einen CSB+ Baum werden
zum Beispiel 160 Byte empfohlen; je nach Architektur des Mikroprozessors kann sie 512 bis über 1024 Bytes betragen und muss nicht an einer
2er Potenz festgemacht werden.
6
Kapitel 3
Delta-Kodierung im B-Baum
Bereits in The Ubiquitous B-Tree von Douglas Comer [Com79] wird die
Komprimierung der Zeiger als mögliche Optimierung von B-Bäumen vorgeschlagen. Um ihre Größe in den Knoten zu verringern, werden sie in
Abhängigkeit zu einem Offset gespeichert. Der damit eingesparte Platz
ermöglicht es, mehr Schlüssel-Zeiger-Paare unterzubringen und einen höheren Verzweigungsgrad zu realisieren. Als weitere Konsequenz reduziert
sich die Höhe des Baumes. Bei einer Suche müssen weniger Stufen abgearbeitet werden und die Suchperformance steigt.
Diese Arbeit beschreibt, wie sich ein ähnliches Prinzip der Komprimierung auf Schlüssel übertragen lässt. Durch eine vom Elternknoten abhängige Delta-Kodierung lassen sich in einem ersten Schritt kleinere
Schlüsselteile berechnen. Anschließend kann - im Vergleich zum Speicherverbrauch bei Verwendung von primitiven Datentypen - durch eine
längenkodierte Speicherung Platz eingespart werden, wenn ausschließlich
die tatsächlich erforderlichen Bytes für einen Schlüssel abgelegt werden.
Bei den Zeigeradressen ist ein analoges Vorgehen möglich.
Eine Grundvoraussetzung für die in dieser Arbeit vorgestellte DeltaKodierung von B-Bäumen ist die Verwendung von Integer-Werten für
Schlüssel.
3.1
3.1.1
Der Delta-kodierte B-Baum
Grundlagen und Definition
Abbildung 3.1 und 3.2 zeigen denselben B-Baum, ohne und mit DeltaKodierung. Die in Abbildung 3.2 an den Kanten notierten Werte stellen
die tatsächlichen Schlüssel dar. Sie ergeben sich durch Aufsummierung
der Teilschlüssel bei der Traversierung. Diese Werte müssen lediglich
temporär gespeichert werden und sind für alle Algorithmen des Delta7
KAPITEL 3. DELTA-KODIERUNG IM B-BAUM
kodierten B-Baumes grundlegend. Im Folgenden wird der tatsächliche
Schlüsselwert im Elternknoten auch als Offset bezeichnet. Um Änderungen an Algorithmen erklären zu können, werden die tatsächlichen
Schlüsselwerte in den Abbildungen der folgenden Abschnitte angefügt.
In den Knoten eines Delta-kodierten B-Baumes sind lediglich Differenzen
gespeichert (siehe Abb. 3.2). Diese ergeben sich aus der Subtraktion des
Offsets und des ursprünglichen Schlüssels. Analog dazu lässt sich jeder
Schlüssel eines Delta-kodierten B-Baumes durch Addition des eben definierten Offsets zusammen mit dem Wert im Knoten wieder herstellen.
Abbildung 3.1: Unkodierter B-Baum.
0
0
5
10
32
18
18
25
28
32
42
60
Abbildung 3.2: B-Baum mit Delta-Kodierung. An den Kanten sind die
tatsächlichen Schlüsselwerte annotiert.
Der Delta-kodierte B-Baum wird folgendermaßen definiert:
Definition. Ein Delta-kodierter B-Baum ist ein Mehrwege Suchbaum.
Für jeden nicht leeren Delta-kodierten B-Baum gilt:
• Alle Knoten im Baum besitzen dieselbe vorgegebene Knotengröße.
• Jeder Knoten außer der Wurzel ist mindestens bis zur Hälfte gefüllt.
• Die Wurzel enthält mindestens einen Schlüssel und ist unkodiert.
• Innere Knoten enthalten Schlüssel und Zeiger, welche in variabler
Länge abgespeichert werden.
• Zeiger von inneren Knoten referenzieren weitere Kindknoten.
8
KAPITEL 3. DELTA-KODIERUNG IM B-BAUM
• Blattknoten enthalten lediglich Schlüssel. Diese werden in variabler
Länge abgespeichert.
• Die Schlüssel jedes Knotens werden als Differenzen zum Wert des
Separatorschlüssels des Elternknotens gespeichert.
• Blätter befinden sich alle auf demselben Level.
Zusätzlich werden für die Korrektheit der in den folgenden Kapiteln beschriebenen Algorithmen zwei implizite Annahmen getroffen:
• Ohne Beschränkung der Allgemeinheit hat der dem ersten Zeiger
zugeordnete Schlüssel den Wert Null.
• Schlüssel verwenden den positiven Zahlenbereich.
3.1.2
Die Suche im Baum
Für die Suche wird ein bereits aufgebauter, mehrstufiger B-Baum angenommen. Die sich durch die Kodierung ergebenden Unterschiede werden
im Zuge dieses Abschnittes erläutert.
Der Pseudocode von Algorithmus 1 beschreibt den Suchvorgang. Wie
beim normalen B-Baum beginnt dieser im Wurzelknoten. Als erster Knoten besitzt er kein Offset; alle sich in ihm befindlichen Schlüssel sind unkodiert gespeichert und können ohne Modifikation durchsucht werden.
Existiert der gesuchte Schlüssel k dort nicht (und ist die Wurzel kein
Blatt), wird dessen Vorgänger gesucht. Dieser Schlüssel wird in der Variable kp gespeichert; der zugehörige Zeiger verweist auf das nächste zu
ladende Kind.
Durch die Addition von kp zu dem bestehenden Offset in der keyOf f set
Variable lässt sich jeder Schlüssel im aktuellen Knoten rekonstruieren.
Im Pseudocode wird der Suchschlüssel analog dazu in jedem Durchlauf
um den Wert von keyOffset verringert in currentKey gespeichert und
kann direkt mit den im Knoten gespeicherten Werten verglichen werden.
Der eben beschriebene Ablauf wiederholt sich, bis der Schlüssel gefunden
wurde oder die Suche ohne Erfolg in einem Blatt endet. In diesem Fall
muss abschließend festgestellt werden, ob der Blattknoten den reduzierten Schlüssel currentKey enthält.
9
KAPITEL 3. DELTA-KODIERUNG IM B-BAUM
Algorithmus 1 Suche eines Schlüssels
Eingabe: Wurzelknoten des Baumes; gesuchter Schlüssel k.
Ausgabe: true wenn sich k im Baum befindet, ansonsten f alse.
function contains(k)
currentN ode ← root
keyOf f set ← 0
while currentN ode ist kein Blatt do
currentKey ← k − keyOf f set
if currentKey ∈ currentN ode then
return true
end if
kp ← Vorgänger von currentKey in currentN ode
keyOf f set ← keyOf f set + kp
currentN ode ← Lade Knoten referenziert durch Zeiger von kp
end while
. Schlüssel wurde in keinem inneren Knoten gefunden
currentKey ← k − keyOf f set
result ← (currentKey ∈ currentN ode)
return result
end function
Abbildung 3.3 zeigt eine exemplarische Suche nach Algorithmus 1. Es
wird dabei angenommen, dass eine erfolglose Suche im aktuellen Knoten
den Vorgängerschlüssel zusammen mit dem zugeordneten Zeiger auf das
nächste Kind liefert.
1.) 62 in node?
32
32
62 > 32
2.) 30 in node? (i.e. 62 - 32)
42
60
30 > 28
3.) 2 in node? (i.e. 30 - 28)
Abbildung 3.3: Suche nach Schlüssel 62.
10
KAPITEL 3. DELTA-KODIERUNG IM B-BAUM
3.1.3
Einfügen von Schlüsseln
Einfügen ohne Überlauf
Das Einfügen der Schlüssel wird im Folgenden auf Grundlage des in Abschnitt 3.1.2 vorgestellten Suchalgorithmus erörtert. Ein Einfügevorgang
bei dem kein Split notwendig ist, wird in Abbildung 3.4 veranschaulicht.
Die Schritte 1 bis 3 zeigen die Suche nach jenem Blatt, in dem der dekrementierte Schlüssel einzufügen ist. In jedem Zwischenschritt wird das
Offset (der an den Zeigern angegebene Wert) vom Suchschlüssel abgezogen und der nächste Kindknoten bestimmt. Im Blatt wird dieser Wert
abschließend eingefügt. Den daraus resultierenden Baum zeigt Abbildung
3.5.
a)
1.) insert(51)
2.) insert(19) (51 - 32)
32
32
42
3.) insert(9)
(51 - 42)
60
Abbildung 3.4: Einfügen des Schlüssels 51 (ohne Überlauf).
b)
32
32
42
60
Abbildung 3.5: Baum nach Einfügen von Schlüssel 51.
11
KAPITEL 3. DELTA-KODIERUNG IM B-BAUM
Einfügen mit Überlauf
Die Delta-Kodierung macht eine Anpassung des Split-Algorithmus erforderlich. Abbildung 3.6 zeigt alle Phasen des Ablaufs.
a)
+54
b)
+22 (54 - 32)
32
+12 (54 - 42)
32
42
32
kl
32
+k l
10 12
60
km
c)
d)
32
32
32
ks
42
-k m
12
60
32
42
52
60
Abbildung 3.6: Überlauf mit anschließendem Split.
Nachdem der Suchalgorithmus das korrekte Blatt für das Einfügen gefunden hat, kommt es dort zum Überlauf (a und b in Abb. 3.6). Der in
der Mitte des Knotens liegende Schlüssel km wird entfernt. Zusammen
mit der aus dem Elternknoten stammenden Differenz kl ergibt dieser
den neuen, im Elternknoten einzufügende Separator ks (Abb. 3.6c). Um
abschließend die Korrektheit des rechten Blattes bezüglich des neuen
Separators ks im Elternteil sicher zu stellen, wird von jedem Schlüssel
des rechten Teils km subtrahiert. Dieser Schritt wird im Folgenden als
Neukodierung des Knotens bezeichnet. Das Resultat der Split-Operation
zeigt Abbildung 3.6d.
Bei weiteren Überläufen kann sich der Split bis in die Wurzel fortsetzen.
Änderungen im Baum bleiben dabei lokal begrenzt; ausschließlich die
Neukodierung des neuen rechten Knotens und das Abändern des Separators bezüglich des Offsets des Elternknotens sind erforderlich.
12
KAPITEL 3. DELTA-KODIERUNG IM B-BAUM
3.1.4
Umverteilung von Schlüsseln
Mit der Umverteilung (oder kleine Rotation) lässt sich beim Löschen
von Schlüsseln aus dem Baum eine aufwändigere große Rotation abwenden. Analog dazu kann die Umverteilung beim Einfügen ein vorzeitiges
Splitten verhindern.
Die Linksrotation
Die Abbildung 3.7a zeigt einen Teilbaum, bei dem das Einfügen in das
rechte Blatt einen Überlauf erzeugen würde. Durch das Verschieben seines ersten Schlüssels in den linken Geschwisterknoten ist ein Aufspalten
nicht notwendig.
a)
b)
...
...
22
4 5
10
54
1
Abbildung 3.7: Rotation nach links.
Lediglich die zwei Geschwister- und der Elternknoten sind von der Operation betroffen: Der ursprüngliche Separator im Elternknoten wird an die
letzte Stelle des linken Geschwisterknotens, korrigiert um das dort geltende Offset, angefügt. Der neue Separator im Elternknoten wird durch
Addition des ursprünglichen Separators und des ersten Schlüssels des
rechten Geschwisterknotens erzeugt. Abschließend sind dort alle verbliebenen Werte bezüglich dieses neuen Separatorschlüssels zu kodieren. Dabei wird von jedem die Differenz von aktualisiertem und ursprünglichen
Separator abgezogen. Abbildung 3.7b zeigt den Teilbaum nach der Rotation.
Im Falle der inneren Knoten wird zusätzlich der erste Zeiger des rechten
Geschwisterknotens an die letzte Stelle des linken Geschwisterknotens
geschoben.
Zusätzlich ist bei der Einfüge-Umverteilung noch auf eine korrekte Reihenfolge zu achten: Wird die eben beschriebenen Linksrotation durchgeführt und in Abbildung 3.7a zum Beispiel der Schlüssel 53 eingefügt was dort dem Wert 1 entspricht - enthält der Baum als Resultat Schlüssel
in der falschen Anordnung und damit korrupte Daten.
13
KAPITEL 3. DELTA-KODIERUNG IM B-BAUM
Die Rechtsrotation
Die Rechtsrotation erfolgt analog zu der eben beschriebenen Rotation
nach links. Abbildung 3.8a und 3.8b zeigen den Teilbaum vor und nach
der Operation.
a)
...
b)
32
...
32
19
6 7 8 9
2 3
6 7 8
Abbildung 3.8: Rotation nach rechts.
14
51
1 3 4
KAPITEL 3. DELTA-KODIERUNG IM B-BAUM
3.1.5
Löschen von Schlüsseln
Für das Entfernen von Schlüsseln wird eine angepasste Variante des Algorithmus von Bayer [BM70] verwendet. Alternativ lässt sich der von
Cormen et al. in [LRSC01] erörterte Algorithmus verwenden (siehe dazu
Anhang A.2).
Die im Folgenden diskutierten notwendigen Anpassungen lassen sich auf
beide Vorgehensweisen direkt adaptieren.
Im Abschnitt 3.1.2 wurde eine Suchtraversierung skizziert, mit der sich
jener Knoten finden lässt, der den zu löschenden Schlüssel enthält. Der
Algorithmus speichert zusätzlich in jedem Schritt das auf den Knoten
bezogene Schlüsseloffset. Dies ist für die eventuell notwendig werdenden
Reparaturmaßnahmen essentiell.
Der Merge
Um Schlüssel aus dem Baum zu entfernen, ist zusätzlich zu den bereits
beschriebenen Rotationen ein abgeändertes Vorgehen für die Verschmelzung von Knoten erforderlich.
a)
...
b)
...
10 12
Abbildung 3.9: Teilbaum vor und nach dem Verschmelzen von Knoten.
Der Merge ist die reziproke Operation zum Split. Abbildung 3.9a veranschaulicht einen Teilbaum mit Unterlauf. In Umkehrung zum Split wird
der Separator aus dem Elternknoten in den linken Kindknoten geschoben. Er muss dabei um die Differenz von rechten und linken Offset der
Kindknoten verringert werden. Die Schlüssel des rechtem Kindknotens
werden rekodiert und an den linkem Kindknoten angefügt. Bei der Rekodierung wird zu jedem Schlüssel die eben erwähnte Differenz addiert.
Abschließend muss der rechte Kindknoten entfernt werden (Abb. 3.9b).
15
KAPITEL 3. DELTA-KODIERUNG IM B-BAUM
Löschen aus einem Blatt
Wie im normalen B-Baum wird beim Löschen aus einem Blatt der Schlüssel ohne weitere Anpassungen entfernt.
Kommt es zu einem Unterlauf, wird ein Ausgleich über den linken oder
rechten Geschwisterknoten versucht. Ist dies nicht möglich, wird der Merge wie im vorigen Abschnitt beschrieben durchgeführt.
Da die Verschmelzung den ehemaligen Separator in den resultierenden
Knoten zieht, kann durch sie eine weitere Unterlaufbehandlung im Elternknoten erforderlich werden. Falls sich dies bis in eine leerlaufende Wurzel fortzieht, sorgt der Verschmelzungs-Algorithmus implizit für
einen unkodierten neuen Wurzelknoten.
Löschen aus einem inneren Knoten
Für das Löschen aus einem inneren Knoten ist ein komplexeres Vorgehen als beim B-Baum erforderlich. Der zu entfernende Schlüssel besitzt
sowohl die Funktion eines Separators als auch eines Offsets. Daher wird
mindestens ein Knoten neu kodiert. Wie beim normalen B-Baum wird
der Schlüssel durch seinen symmetrischen Vorgänger oder Nachfolger ersetzt.
...
a)
...
b)
32
32
11
...
42
42
4 8
46
50
...
...
43
3 7
43
1 2
46
50
...
Abbildung 3.10: a) Zeigt den Baum vor Löschen des Schlüssels 42 (entspricht dem Wert 10). Wie in b) ersichtlich, sind Rekodierungen lediglich
für die Wurzel und jeden am weitest links stehenden Knoten des Subbaumes notwendig.
Algorithmus 2 auf Seite 18 beschreibt den Löschvorgang. Nach dem Fund
des zu entfernenden Schlüssels in einem inneren Knoten (Fall 2 von Algorithmus 2) erfolgt der Abstieg in das erste Blatt des rechten Subbaumes.
Implizit speichert der Algorithmus bei jedem Ladevorgang eine Referenz auf den Vorgängerknoten. Alle am weitest links stehenden Knoten
des Subbaumes müssen bezüglich des symmetrischen Nachfolgers neu
16
KAPITEL 3. DELTA-KODIERUNG IM B-BAUM
kodiert werden, da sie dasselbe Offset besitzen und deshalb vom zu löschenden Schlüssel abhängig sind. Dies geschieht nach dem Entfernen des
ersten Schlüssels aus dem Blatt, beim Aufstieg zurück in den SubbaumWurzelknoten. Abbildung 3.10 zeigt ein Beispiel des eben beschriebenen
Ablaufes. Abschließend wird im Falle eines Unterlaufes im Blatt, ein (wie
im Abschnitt Löschen aus einem Blatt geschilderter) Korrekturvorgang
durchgeführt.
Wird hingegen der symmetrische Vorgänger als Ersatz für den zu löschenden Schlüssel gewählt, erfolgt eine Suchtraversierung über den linken Subbaum (ohne Rekodierungen). Der zu entfernende Separator wird
durch den letzten Schlüssel des letzten Blattes ersetzt. Damit wird ein
Abstieg in den rechten Teil, zusammen mit einer Rekodierung aller ersten Knoten erforderlich. Daher bedeutet das Löschen mit Hilfe des symmetrischen Vorgängers eine Traversierung über zwei Subbäume. Damit
verbunden ist ein durch das Laden von zusätzlichen Knoten verursachter
Mehraufwand.
3.1.6
Zusammenfassung der Operationen
In den letzten Kapiteln wurden die Unterschiede des Delta-kodierten
B-Baumes zum herkömmlichen B-Baum herausgearbeitet. Ein Suchlauf
benötigt das von der Wurzel aus berechnete Offset als zusätzliche Information. Mit diesem Wert können alle Schlüssel des aktuell besuchten
Knotens rekonstruiert werden.
Das Offset ist für Split, Einfügen neuer Schlüssel und Ausgleichsrotationen essentiell. Es wird durch Split und Rotationen verändert und macht
eine Neukodierung eines Knotens erforderlich.
Beim Entfernen von Schlüsseln wird beim Verschmelzen eine Umkodierung für den zu integrierenden Knoten notwendig. Während das Löschen
aus einem Blatt analog zum B-Baum geschieht, bedarf es beim Entfernen aus dem Inneren der Neukodierung aller ersten Knoten des rechten
Subbaumes.
17
KAPITEL 3. DELTA-KODIERUNG IM B-BAUM
Algorithmus 2 Löschen aus Blatt- und inneren Knoten
Eingabe: Zu entfernender Schlüssel k; Knoten currentN ode in dem
dieser sich kodiert befindet; alle Offsets bis zu diesem Knoten; jeder
traversierte Knoten kennt seinen Elternknoten.
Ausgabe: Modifizierter Baum ohne Schlüssel k.
leaf N ode ← null
function remove(k, currentNode)
currentKey = k − currentN ode.keyOf f set
if currentKey ∈ currentN ode then
if currentN ode ist ein Blatt then
. Fall 1
Entferne currentKey aus currentN ode
Repariere von currentN ode aus, falls Unterlauf
else
. Fall 2
subT reeRoot ← Knoten identifiziert durch currentKey
successor ←decend(subT reeRoot )
newSeparator ← successor − currentN ode.KeyOf f set
Ersetze (currentKey ∈ currentN ode) durch newSeparator
Repariere ausgehend von leaf N ode, falls Unterlauf
end if
end if
end function
function Decend(subTreeRoot) . Methode für Abstieg in Subbaum
current ← subT reeRoot
while current ist kein Blatt do
Lade ersten Knoten nach current
Speichere Offset und Referenz auf Elternknoten in current
end while
leaf N ode ← current
. globale Variable
successor ←(Erster Key in leaf N ode) + leaf N ode.keyOf f set
Entferne ersten Schlüssel aus leaf N ode
repeat
Kodiere current bezüglich successor neu
current.keyOf f set ← successor
Lade den Elternknoten nach current
until current ist nicht der Elternknoten von subT reeRoot
return successor
end function
18
KAPITEL 3. DELTA-KODIERUNG IM B-BAUM
3.2
Die Knotenarchitektur des B-Baumes
Für die Umsetzung des hier vorgestellten B-Baumes wurde eine Schichtenarchitektur gewählt. Mit diesem Prinzip lassen sich sowohl der klassische B-Baum als auch verschiedene Delta-kodierte B-Bäumen erstellen.
Wie in Kapitel 3 beschrieben, erfordert die Delta-Kodierung Knoten,
welche Daten in variabler Länge ablegen können. Die in den folgenden
Abschnitten erörterten Knotenarten mit innerer RB-Baum Struktur, längenkodierter Struktur und längenkodierter Struktur mit Präfixsummen
Optimierung besitzen diese Eigenschaft. Dort gespeicherte Schlüssel oder
Zeiger verwenden ausschließlich die Anzahl an Bytes, welche für ihre
Darstellung erforderlich ist. Die Knoten unterstützen die Speicherung
von 64-Bit Integern und verwenden für diese entsprechend mindestens 1
Byte bis maximal 8 Bytes.
Klassische B-Baum Struktur
Als Vergleichsdatenstruktur wurden Knoten mit einer Array-ähnlichen
Organisation implementiert, die fixierte Längen für Schlüssel und Zeiger
voraussetzen. Durch sie wird ein klassischer B-Baum realisiert.
Die Anzahl der enthaltenen Elemente wird in einem führenden Header
abgelegt. Darauffolgend sind alle Schlüssel in sortierter Reihenfolge als
8 Byte lange Werte gespeichert. Blattknoten enthalten lediglich diese
beiden Felder.
Bei inneren Knoten folgt dieser Anordnung ein unmittelbar anschließender Bereich für Zeiger, die in derselben Reihenfolge wie ihre Schlüssel
abgelegt sind (siehe dazu Abb. 3.11).
size
key1 key2 key3 ... keyn | ptr0 ptr1 ptr2 ptr3 ... ptrn
free space
Abbildung 3.11: Interner Knoten mit Array Struktur (Schlüssel werden
mit keyi , Zeiger mit ptri bezeichnet). Aus Gründen der Übersichtlichkeit
wird auf die Darstellung des Blattknotens verzichtet.
Eine Ausnahme besteht für den ersten Zeiger: Um den Platzverbrauch
für den Schlüssel 0 von 8 Bytes einzusparen, wird dieser als implizit
existierend angenommen. Wie beim herkömmlichen B-Baum werden n
Schlüssel und n + 1 Zeiger gespeichert.
Der erforderliche Platz für Zeiger wird auf 6 Bytes beschränkt. Damit
ist ein Speicherbereich von 248 Adressen für Kindknoten möglich.
Die eindeutigen Byte-Größen von Schlüsseln und Zeigern erlauben performante Implementierungen von Merge, Split, Rekodierung und Rotati19
KAPITEL 3. DELTA-KODIERUNG IM B-BAUM
on. Als Suchalgorithmus in den Knoten wird die binäre Suche verwendet,
zumal die beschriebene Datenorganisation einen wahlfreien Zugriff ohne
zusätzlichen Aufwand realisiert.
Die Array-Struktur hat den offensichtlichen Nachteil, keinen Nutzen aus
der Delta-Kodierung des B-Baumes ziehen zu können; es macht keinen
Unterschied ob der ursprüngliche oder der verkleinerte Wert verwendet
wird.
RB-Baum als innere Struktur
Mit diesem Knotentyp können sowohl Schlüssel als auch Key-Pointer
Paare in einem als Rot-Schwarz Baum organisierten Speicherbereich abgelegt werden. Intern wird für die Speicherung der Werte eine fixed-prefix
Kodierung benutzt, mit der lediglich der tatsächlich erforderliche Platz
kodiert wird. Durch den RB-Baum lässt sich die Suche performant realisieren.
size
free space
k0
k1
k3
k 2 nil
nil nil nil nil
k6
k4
k5
nil nil
k1
k7
nil nil
k8
k9
k 10
k 11
nil nil nil nil
Abbildung 3.12: Red-Black Tree als innere Knotenstruktur.
Eine Besonderheit ergibt sich bei der Ausgleichsrotation nach links: Hier
kann unter bestimmten Voraussetzungen der aktualisierte Separator im
Elternknoten nicht gespeichert werden. Durch diese Rotation ergibt sich
ein höherer Betrag des Schlüssels und damit in manchen Fällen ein größerer Platzbedarf. Bei einem vollen Knoten wird in diesem Zusammenhang
ein zusätzlicher Split notwendig.
Wie in Kapitel 3.1.4 beschrieben, kodiert die Rechtsrotation in jedem
Rotationsschritt den gesamten rechte Knoten neu. Dabei vergrößern sich
die Schlüssel im Betrag und benötigen analog zur Linksrotation unter
Umständen mehr Platz im Knoten als vorhandenen ist. In diesem Fall
stellt ein Rollback-Algorithmus die Konsistenz der Daten wieder her.
Beim Merge erfolgt eine Abschätzung für eine erfolgreiche Durchführung
der Operation. Wieder setzt ein Rollback den Knoten in den Ursprungs20
KAPITEL 3. DELTA-KODIERUNG IM B-BAUM
zustand zurück, sollte dabei zu wenig Speicher im Zielknoten zur Verfügung stehen.
Wie in Kapitel 3.1.5 beschrieben, erfordert eine große Rotation einige
Umkodierungen für den rechten Subbaum. Ein Problem bezüglich der
Rekodierung in Zusammenhang mit zu wenig Speicherplatz tritt hier
nicht auf; alle notwendigen Berechnungen haben kleinere Werte in den
Knoten zum Resultat.
Entsprechend wie beim Split kann zu wenig Platz für den neue Separator
im Elternknoten vorhanden sein, was sich durch ein erneutes Aufspalten
korrigieren lässt.
Knoten mit längenkodierten Daten
Der Platzbedarf von Schlüssel und Zeiger wird bei dieser Knotenart in
sogenannten Subheadern gespeichert. Wie in Abbildung 3.13 zu sehen ist,
kodieren 3 Bits die Längeninformation (wobei 0 für die Länge von einem
Byte steht). Schlüssel und Zeiger werden vom Ende des zur Verfügung
stehenden Speicherbereichs aus geschrieben. Beim Einfügen wachsen daher Subheader und Einträge innerhalb eines Knotens aufeinander zu.
size
subheader0 ... subheadern
highest bits
23 22 21
20 19 18
17 16 15
subheader
14 13 12
11 10 09
08 07 06
05 04 03
free space
ptrn keyn ... ptr1 key1 ptr0 key0
lowest bits
02 01 00
000 000 000 000 000 000 000 000
byte 0
byte 1
byte 2
Abbildung 3.13: Längenkodierung von Knotendaten realisiert durch Subheader.
Split und Rotationen verhalten sich analog zu den Knoten mit RBBaum Struktur. Der Merge besitzt eine exakte Berechnungsmethode für
die Vorhersage seiner Durchführbarkeit (und benötigt keinen RollbackMechanismus).
Als Einschränkung wird bei diesem Knotentyp ausschließlich eine lineare
Suche verwendet. Deswegen folgt bei inneren Knoten jedem Schlüssel
unmittelbar der zugehörige Zeiger.
Längenkodierung kombiniert mit Präfixsummen
Dieser Knotentyp erlaubt es, die binäre Suche zusammen mit der Längenkodierung von Werten zu verwenden. Die Längeninformationen werden in
Subheadern nach einem, für die Berechnung von Präfixsummen optimierten Schema verteilt. Beim Einlesen des Knotens erfolgt die Speicherung
21
KAPITEL 3. DELTA-KODIERUNG IM B-BAUM
aller Teilsummen in einem temporären Array. Damit lässt sich die Position eines Schlüssels oder Zeigers im Speicherbereich direkt aus dem Feld
auslesen. Durch die jeweils nachfolgende Teilsumme wird anschließend
die Größe berechnet.
size
subheader0 ... subheadern
highest bits
63
60
57
54
51
48
45
42
39
free space
subheader
36
33
30
27
24
ptrn ... ptr1 ptr0 | keyn ... key1 key0
21
18
15
12
9
6
lowest bits
3
0
u000 111 000 111 000 111 000 111 000 111 000 111 000 111 000 111 000 111 000 111 000
bits
06
05
04
03
02
01
00
slot 0 - 6
13
12
11
10
09
08
07
slot 7 - 13
19
18
17
16
15
14
slot 14 - 20 20
byte 7
byte 6
byte 5
byte 4
byte 3
byte 2
byte 1
byte 0
Abbildung 3.14: Eine optimierte Verteilung der Längeninformationen ermöglicht die performante Berechnung von Präfixsummen. Die Längen
von Schlüsseln (oder Zeigern) werden in „Slots“ von 3 Bits gespeichert.
Wie beim vorhergehenden Knotentyp werden Schlüssel und Zeiger vom
Ende des Knotens aus gespeichert. Schlüssel werden in Hinsicht auf die
binäre Suche nacheinander abgelegt; darauf folgt der Bereich für die Zeiger.
Die Operationen Merge, Split, Rotation und Rekodierung verhalten sich
völlig analog zu dem im vorigen Abschnitt vorgestellten Knotentyp.
Zusammenfassung der Knotenarten
Mithilfe der in diesem Abschnitt beschriebenen Knotentypen werden
Delta-kodierte B-Bäume, mit voneinander abweichenden inneren Strukturen realisiert.
Bei den als Arrays organisierten Knoten ist es durch die fixierte Länge
der gespeicherten Daten nicht möglich, die Datenkompression zu implementieren. Dies erlaubt eine über Rot-Schwarz Bäume umgesetzte innere
Struktur, eine längenkodierte Speicherung oder eine für Präfixsummen
optimierte Abwandlung.
22
Kapitel 4
Evaluierung
Für die Evaluierung wurde ein B-Baum mit Delta-Kodierung, zusammen
mit allen im Kapitel 3.2 vorgestellten Knotenstrukturen in Java implementiert. Die Speicher-Allokierung für Knoten wird von einem eigenen
Speichermanager realisiert. Dieser erlaubt einen direkten Zugriff auf den
Systemspeicher, ohne Verwendung des automatischen Speichermanagements von Java.
Es wurde eine Benchmarksuite erstellt, mit der eine Gegenüberstellung
von unterschiedlichen Delta-kodierten B-Baum Typen bezüglich Speicherverbrauch und Geschwindigkeit möglich ist. Darüber hinaus stellt
diese Vergleiche mit dem herkömmlichen B-Baum, dem Java TreeSet
und dem C++ STL-Set in Hinblick auf Einfüge- und Suchvorgänge an.
4.1
Testumgebung
Die Tests wurden auf einem 8-Kern AMD FX-8350 Prozessor mit 4 GHz
und 16 GB Hauptspeicher durchgeführt. Die Cache-Line dieser 64-Bit
CPU ist 64 Byte lang. Level 1 Data Cache und Level 1 Instruction Cache
besitzen jeweils eine Gesamtgröße von 128 KB und 256 KB. Bei diesem
Mikroprozessor sind jeweils zwei Kerne zu einem sogenannten Modul
zusammengefasst, wobei jedes über einen Level 2 Cache von 2048 KB
verfügt. Alle Kerne teilen sich einen 8 MB großen Level 3 Cache.
Als Betriebssystem kam openSUSE 12.3 mit Linux Kernel 3.7.10 zum
Einsatz. Für die Ausführung der Java Programme wurde das Oracle
OpenJDK Version 1.7.0 Update 40 verwendet. Über die VM-Optionen
-disableassertions -XX:MaxDirectMemorySize=2g -Xmx10g wurde ausreichend Speicher für die Vergleichstests zur Verfügung gestellt. Für die
Übersetzung der C-Programme wurde g++ der GNU Compiler Collection Version 4.7.2 verwendet. Die Quellen wurden mit der Kompileroption
-O3 umgewandelt.
23
KAPITEL 4. EVALUIERUNG
4.2
Methodik der Benchmarks
Es werden der Speicherverbrauch und die Leistung bei Aufbau und Suche
auf einem einzelnen CPU-Kern untersucht.
Die Aufbaubenchmarks erfassen die Leistung für das Einfügen einer definierten Menge von Schlüsseln in einen leeren Baum. Die Leistungsmessung des Suchvorgangs erfolgt anschließend unter Verwendung derselben
Menge. Beide Schritte werden sowohl für Delta-kodierte B-Bäume als
auch für B-Baum, TreeSet und STL-Set für das Intervall von 100.000 bis
12 Millionen Schlüssel durchgeführt.
Die betrachteten B-Bäume werden in den nachfolgenden Ausführungen
in Übereinstimmung mit ihren inneren Knotenstrukturen B-Baum, RedBlack-, Längenkodierter- und Präfix-Summen δ-B-Baum genannt.
4.3
Der Speicherverbrauch
Die in dieser Arbeit beschriebene Kodierung ist für den Speicherverbrauch von Vorteil, falls die Schlüsselverteilung eine Kompression erlaubt: Liegen die Schlüssel in einem Wertebereich nahe beieinander, bilden sie kleine Differenzen und können effizient im Baum gespeichert werden. Sind sie zufällig über den gesamten Wertebereich verteilt, ergeben
sich große Differenzen, welche im Worst Case gleich viel Speicherplatz
wie die ursprünglichen Schlüssel verbrauchen.
Der Speicherverbrauch für B-Bäume wird über den eigenen Speichermanager festgestellt. Beim TreeSet erfolgt dies über Java-Runtime Methoden. Um möglichst genaue Resultate zu erhalten, wird der Garbage
Collector vor jedem Durchgang mehrfach aufgerufen.
24
KAPITEL 4. EVALUIERUNG
4.3.1
Verbrauch beim Aufbau
Abbildung 4.1 zeigt den für ein lineares Einfügen von Schlüsseln notwendigen Speicherbedarf. Auf der y-Achse des Diagramms ist der Verbrauch
in Megabyte angegeben; die Anzahl der Schlüssel ist auf der x-Achse
aufgetragen.
Für das TreeSet ist hierbei zusätzlicher Speicher notwendig, der erst nach
Abschluss der Operation wieder freigegeben wird.
800 MB
700 MB
600 MB
B-Tree
RB δB-Tree
Längenkodierter δB-Tree
Präfix Summen δB-Tree
Java TreeSet
500 MB
400 MB
300 MB
200 MB
100 MB
0 MB
0 Mio.
2 Mio.
4 Mio.
6 Mio.
Schlüssel
8 Mio.
10 Mio.
12 Mio.
Abbildung 4.1: Speicherverbrauch beim Aufbau der Bäume (unter Verwendung einer lineare Schlüsselmenge).
25
KAPITEL 4. EVALUIERUNG
4.3.2
Verbrauch nach dem Aufbau
Beim Suchvorgang wird ausschließlich der tatsächliche Verbrauch in Abhängigkeit zur verwendeten Schlüsselmenge gemessen. Die Abbildungen
4.2 bis 4.4 zeigen den Speicherverbrauch für verschiedene Mengen.
Menge aus direkt aufeinanderfolgenden Schlüsseln
Abbildung 4.2 zeigt den Speicherbedarf für eine lineare Menge von Schlüsseln. Direkt aufeinanderfolgende Schlüssel sind für die Delta-Kodierung
optimal. Wie in der Abbildung zu sehen ist, reduziert sich der Speicherbedarf für alle δ-B-Bäume im Vergleich zum B-Baum um 2/3. Das TreeSet
hat gegenüber jedem δ-B-Baum einen bis zu 8-fach höheren Speicherverbrauch.
300 MB
B-Tree
RB δB-Tree
250 MB
Längenkodierter δB-Tree
Präfix Summen δB-Tree
Java TreeSet
200 MB
150 MB
100 MB
50 MB
0 MB
0 Mio.
2 Mio.
4 Mio.
6 Mio.
Schlüssel
8 Mio.
10 Mio.
Abbildung 4.2: Speicherverbrauch linearer Schlüsselmengen.
26
12 Mio.
KAPITEL 4. EVALUIERUNG
Randomisierte Schlüsselmengen
Abbildung 4.3 zeigt den Speicherverbrauch einer Menge „nahe“ aufeinanderfolgender Schlüssel; jeder Schlüssel besitzt einen zufälligen Abstand
von maximal 1000 zu seinem Vorgänger. In diesem Fall benötigen der
Längenkodierte- und Präfix-Summen δ-B-Baum 3/4 des Speicherbedarfes des klassischen B-Baumes.
300 MB
B-Tree
RB δB-Tree
250 MB
Längenkodierter δB-Tree
Präfix Summen δB-Tree
Java TreeSet
200 MB
150 MB
100 MB
50 MB
0 MB
0 Mio.
2 Mio.
4 Mio.
6 Mio.
Schlüssel
8 Mio.
10 Mio.
12 Mio.
Abbildung 4.3: Speicherverbrauch „nahe“ aufeinanderfolgender Schlüssel.
300 MB
B-Tree
RB δB-Tree
250 MB
Längenkodierter δB-Tree
Präfix Summen δB-Tree
Java TreeSet
200 MB
150 MB
100 MB
50 MB
0 MB
0 Mio.
2 Mio.
4 Mio.
6 Mio.
Schlüssel
8 Mio.
10 Mio.
12 Mio.
Abbildung 4.4: Speicherverbrauch zufällig verteilter Schlüssel.
27
KAPITEL 4. EVALUIERUNG
Abbildung 4.4 zeigt den Speicherverbrauch einer Menge von zufälligen
Schlüsseln aus dem Intervall [0; 263 − 2]. Wie am Anfang des Abschnittes beschrieben wurde, ist diese Situation für Delta-kodierte B-Bäume
ungünstig. Deshalb nähert sich der Verbrauch der δ-B-Bäume dem klassischen B-Baum an.
4.4
Benchmarks über Einfügeoperationen
4.4.1
Menge aus direkt aufeinanderfolgenden Schlüsseln
Abbildung 4.5 zeigt die Ergebnisse für ein lineares Einfügen von Schlüsseln. Auf der y-Achse des Diagramms ist die ermittelte Leistung in Millionen Operationen pro Sekunde angegeben; die Anzahl der Schlüssel ist
auf der x-Achse aufgetragen.
Die Rot-Schwarz Bäume des STL-Sets werden am schnellsten aufgebaut.
Das TreeSet von Java zeigt Leistungseinbrüche, während alle δ-B-Bäume
stabile Performance bieten. Der B-Baum führt keine Längenkodierung
durch und erreicht daher höhere Werte als die übrigen δ-B-Baum Varianten.
6
B-Tree
Präfix-Summen δB-Tree
Längenkodierter δB-Tree
C++ STL Set
Java TreeSet
RB δB-Tree
Millionen Operationen pro Sekunde
5
4
3
2
1
0
0 Mio.
2 Mio.
4 Mio.
6 Mio.
Schlüssel
8 Mio.
10 Mio.
12 Mio.
Abbildung 4.5: Einfügen von aufeinanderfolgenden Schlüsseln.
4.4.2
Randomisierte Schlüsselmengen
Abbildung 4.6 zeigt das Einfügen einer zufällig durchmischten Menge von
ursprünglich direkt aufeinanderfolgenden Schlüsseln. Die Ursprungsmen28
KAPITEL 4. EVALUIERUNG
ge startet ab einem Offset von 4 Milliarden. Der zusätzliche Aufwand für
die Längenkodierung schlägt sich bei Red-Black-, Längenkodierten- und
Präfix-Summen δ-B-Bäumen in niedrigeren Leistungswerten nieder.
5
Millionen Operationen pro Sekunde
4.5
B-Tree
Präfix-Summen δB-Tree
Längenkodierter δB-Tree
C++ STL Set
Java TreeSet
RB δB-Tree
4
3.5
3
2.5
2
1.5
1
0.5
0
0 Mio.
2 Mio.
4 Mio.
6 Mio.
Schlüssel
8 Mio.
10 Mio.
12 Mio.
Abbildung 4.6: Zufälliges Einfügen von aufeinanderfolgenden Schlüsseln.
5
Millionen Operationen pro Sekunde
4.5
B-Tree
Präfix-Summen δB-Tree
Längenkodierter δB-Tree
C++ STL Set
Java TreeSet
RB δB-Tree
4
3.5
3
2.5
2
1.5
1
0.5
0
0 Mio.
2 Mio.
4 Mio.
6 Mio.
Schlüssel
8 Mio.
10 Mio.
12 Mio.
Abbildung 4.7: Zufälliges Einfügen von „nahe“ gelegenen Schlüsseln.
Abbildung 4.7 zeigt den Einfügevorgang einer zufällig durchmischten
Menge von vormals „nahe“ aufeinanderfolgenden Schlüsseln; jeder Schlüs29
KAPITEL 4. EVALUIERUNG
sel der sortierten Ursprungsmenge besitzt einen zufälligen Abstand von
maximal 1000 zu seinem Vorgänger. Nach Erstellung wird die Menge
durchmischt. Die Leistungswerte des TreeSets und des B-Baumes sind
bei steigenden Schlüsselzahlen annähernd identisch.
4.4.3
Rein zufällige Schlüsselmenge
Die Abbildung 4.8 zeigt das Einfügen von zufälligen Schlüsseln aus dem
Intervall [0; 263 − 2]. Bei zunehmender Schlüsselanzahl übertrifft der BBaum jede andere Struktur.
5
Millionen Operationen pro Sekunde
4.5
B-Tree
Präfix-Summen δB-Tree
Längenkodierter δB-Tree
C++ STL Set
Java TreeSet
RB δB-Tree
4
3.5
3
2.5
2
1.5
1
0.5
0
0 Mio.
2 Mio.
4 Mio.
6 Mio.
Schlüssel
8 Mio.
10 Mio.
12 Mio.
Abbildung 4.8: Einfügen von zufälligen Schlüsseln.
4.5
Benchmarks über Suchvorgänge
Der Suchvorgang wurde mit denselben Schlüsselmengen durchgeführt.
Werden Schlüssel direkt aufeinanderfolgend gesucht, bietet das TreeSet
die beste Leistung (Abb. 4.9). Im Falle des wahlfreien Suchens ist der BBaum am performantesten (Abb. 4.10 bis 4.12). Die Leistung des TreeSets und des Längenkodierten δ-B-Baumes nähern sich bei steigender
Schlüsselanzahl aneinander an. Das STL-Set verliert in diesen Fällen
stetig an Effizienz.
30
KAPITEL 4. EVALUIERUNG
14
12
Millionen Operationen pro Sekunde
B-Tree
Präfix-Summen δB-Tree
Längenkodierter δB-Tree
C++ STL Set
Java TreeSet
RB δB-Tree
10
8
6
4
2
0
0 Mio.
2 Mio.
4 Mio.
6 Mio.
Schlüssel
8 Mio.
10 Mio.
12 Mio.
Abbildung 4.9: Suche nach direkt aufeinanderfolgenden Schlüsseln.
5.5
5
4.5
Millionen Operationen pro Sekunde
B-Tree
Präfix-Summen δB-Tree
Längenkodierter δB-Tree
C++ STL Set
Java TreeSet
RB δB-Tree
4
3.5
3
2.5
2
1.5
1
0 Mio.
2 Mio.
4 Mio.
6 Mio.
Schlüssel
8 Mio.
10 Mio.
12 Mio.
Abbildung 4.10: Wahlfreier Suchvorgang auf direkte Schlüsselmengen.
31
KAPITEL 4. EVALUIERUNG
5
Millionen Operationen pro Sekunde
4.5
B-Tree
Präfix-Summen δB-Tree
Längenkodierter δB-Tree
C++ STL Set
Java TreeSet
RB δB-Tree
4
3.5
3
2.5
2
1.5
1
0.5
0 Mio.
2 Mio.
4 Mio.
6 Mio.
Schlüssel
8 Mio.
10 Mio.
12 Mio.
Abbildung 4.11: Wahlfreier Suchvorgang auf „nahe“ gelegene Schlüssel.
5.5
5
4.5
Millionen Operationen pro Sekunde
B-Tree
Präfix-Summen δB-Tree
Längenkodierter δB-Tree
C++ STL Set
Java TreeSet
RB δB-Tree
4
3.5
3
2.5
2
1.5
1
0.5
0 Mio.
2 Mio.
4 Mio.
6 Mio.
Schlüssel
8 Mio.
Abbildung 4.12: Zufällige Suche.
32
10 Mio.
12 Mio.
KAPITEL 4. EVALUIERUNG
4.6
Zusammenfassung der Ergebnisse
Im Vergleich zum klassischen B-Baum kann bei den hier verwendeten
Benchmarks der Speicherbedarf für Daten bei Delta-kodierten B-Bäumen
um bis zu 2/3 geringer sein. Ein Performancevorteil ergibt sich daraus
allerdings trotz des dadurch erhöhten Verzweigungsgrades nicht. Grund
dafür ist die in der Implementierung eingesetzte Schichtenarchitektur.
Der Längenkodierte δ-B-Baum bietet den größten Vorteil und erreicht bei
wahlfreien Suchvorgängen annähernd die Leistungsfähigkeit des TreeSets
von Java.
33
Kapitel 5
Zusammenfassung und
Ausblick
In dieser Arbeit wurden die Grundlagen für Delta-kodierte B-Bäume dargelegt und verschiedene Implementierungen hinsichtlich ihrer Geschwindigkeit mit dem Java TreeSet und STL-Set evaluiert. Verschiedene BBaum Typen werden durch unterschiedliche Knotenarten realisiert und
besitzen ein abweichendes Verhalten in Hinblick auf Leistung und Speicherverbrauch.
So übersteigt die Performance des klassischen B-Baumes in fast allen
Anwendungsfällen die des TreeSets. Im Vergleich zu den δ-kodierten BBäumen unterstützt dieser aber keine Schlüsselkompression. Sein Speicherverbrauch bleibt dennoch geringer als der des TreeSets.
Diese Leistungswerte werden von δ-B-Bäumen, welche durch eine Längenkodierung eine Reduktion der Daten ermöglichen, nicht erreicht. Die
Gründe dafür sind die komplexeren Algorithmen und ein zusätzlicher
Overhead, verursacht durch die Schichtenarchitektur der Implementierung. Der Vorteil dieser Bäume liegt im nochmals reduzierten Speicherverbrauch. Speziell für den Fall wenn Schlüssel bezüglich ihrer Werte
nahe beieinander liegen, kann durch die Delta-Kodierung ein hoher Verzweigungsgrad im Baum erreicht werden, was eine bessere Suchleistung
zur Folge hat.
Als Ausblick ließe sich die Performance bei ausschließlicher Verwendung eines einzelnen Knotentyps durch das Entfallen der Schichtenarchitektur steigern. Bei Verwendung mehrere CPU-Kerne für den Suchvorgang, könnte die Gesamtleistung ebenso gesteigert werden. Die Implementierung, auf die diese Arbeit aufbaut, unterstützt derzeit lediglich
einen Kern. Darüber hinaus könnte die Leistung der Präfix-Summen δB-Bäume verbessert werden, falls in der zukünftigen Version 9 von Java
eine GPU-Beschleunigung konkretisiert wird. Ihre innere Struktur ist für
35
KAPITEL 5. ZUSAMMENFASSUNG UND AUSBLICK
SIMD-Parallelismus optimal ausgelegt. Zusätzlich lassen sich Kopiervorgänge in den Speicher der GPU/APU durch den reduzierten Platzbedarf
schneller durchführen.
36
Appendix
A.1
Compilieren des HotSpot-Disassemblers
Für das Compilieren des Disassembler-Moduls der OpenJDK unter Linux
müssen folgende beiden Quellcode Archive geladen werden:
• Java-Quellcode von http://download.java.net/openjdk/jdk7/
• GNU-Binutils von http://ftp.gnu.org/gnu/binutils/
Die Version der GNU-Binutils muss mit der vom System verwendeten
übereinstimmen.
Beide Archive werden in ein beliebiges Verzeichnis entpackt. Im Folgenden werden die Versionsnummer 1.7.0_50 für die Java und 2.23.1 für die
Binutils Quellen angenommen.
Die sich in /openjdk/hotspot/src/share/tools/hsdis/ befindliche Datei hsdis.c muss folgendermaßen editiert werden:
• Unmittelbar nach dem Einbinden der eigene Headerdatei durch
#i n c l u d e " h s d i s . h"
muss
#i n c l u d e <c o n f i g . h>
eingefügt werden (noch vor allen anderen System-Headerdateien)
• Die Zeile
#i n c l u d e <s y s d e p . h>
muss durch folgende beiden includes ersetzt werden:
#i n c l u d e <s t r i n g . h>
#i n c l u d e <e r r n o . h>
37
APPENDIX A.2. LÖSCHEN NACH CORMEN ET AL.
Der Kopf der Datei sollte wie folgt aussehen:
#include "hsdis.h"
#include <config.h>
#include <string.h>
#include <errno.h>
#include <libiberty.h>
...
Anschließend wird in das /openjdk/hotspot/src/share/tools/hsdis/
Verzeichnis gewechselt und dort make ausgeführt.
$ make BINUTILS=/home/user/tmp/binutils-2.23.2
Dieser Befehl definiert zusätzlich den Ort der Binutils. Nach erfolgter
Kompilierung, existiert ein Verzeichnis mit Namen build. Dort befindet
sich der Architektur entsprechend ein Unterverzeichnis mit der .so Datei
(z.B. build/linux-amd64/hsdis-amd64.so).
Abschließend wird diese nach $JAVA_HOME/jre/lib/amd64/server kopiert.
Die folgende Zeile führt die Datei bench.jar aus und zeigt für die Klasse BenchmarkAllInOne und die Methode nodeSearch den AssemblerOutput:
$ java -server -XX:+UnlockDiagnosticVMOptions \
-XX:PrintAssemblyOptions=intel \
’-XX:CompileCommand=print,*BenchmarkAllInOne.nodeSearch’ \
-jar bench.jar
-XX:PrintAssemblyOption=intel sorgt für die Ausgabe als Intel-Assembler anstatt der voreingestellten AT&T Syntax.
A.2
Löschen nach Cormen et al.
Dieser Algorithmus von Cormen et al. [LRSC01] minimiert die notwendigen Traversierungen im Baum.
Beim Vorgehen nach Bayer [BM70] ist ein rekursiver Aufstieg bis maximal in die Wurzel durch die Unterlaufbehandlung sowohl beim Löschen
aus Blättern als auch aus inneren Knoten möglich.
38
APPENDIX A.3. DER DELTA-KODIERTE B+ BAUM
Analog zu diesem Algorithmus ersetzt das Entfernen eines Schlüssels aus
einem inneren Knoten diesen mit dem direkten Vorgänger oder Nachfolger. Zur Unterscheidung wird sichergestellt, dass der Wurzelknoten
des linken (respektive rechten) Subbaumes in dem sich der Vorgänger
(Nachfolger) befindet, mindestens über die Hälfte voll ist. Ist im weiteren Ablauf eine Unterlaufbehandlung im Subbaum notwendig, kann
sie sich höchstens bis zur Subwurzel ausbreiten. Die Einhaltung dieser
Bedingung wird durch Ausgleichsrotationen aus den Geschwisterknoten
erreicht. Ist dies nicht möglich, kann im Zuge der Verschmelzung beider
Kindknoten der Schlüssel direkt gelöscht werden, ohne eine Ersetzung
vorzunehmen.
Befindet sich der zu entfernende Schlüssel in einem Blatt wird von der
Wurzel aus abwärts bis dorthin die eben beschriebene Reparatur durchgeführt. Im Blatt selbst kann der Schlüssel ohne weitere Behandlung
entfernt werden.
Laut Cormen et al. ist meist eine einzelne Traversierung erforderlich,
da sich die meisten Schlüssel in den Blättern befinden. Ein zweiter Traversierungslauf in Richtung der Wurzel kann beim Löschen aus einem
inneren Knoten notwendig werden.
A.3
Der Delta-kodierte B+ Baum
Ein B+ Baum speichert alle Schlüssel in den Blättern; innere Knoten
dienen ausschließlich als Suchindex. Der Index kann ähnlich wie beim
Delta-kodierten B-Baum abgelegt werden. Es ist möglich die Schlüssel
unkodiert in den Blättern zu speichern. Falls die Blätter als doppelt
verkettete Liste realisiert sind, können Bereichsabfragen mit dieser Voraussetzung ohne Verwendung des Index durchgeführt werden.
Werden die Schlüssel kodiert abgelegt, ist es erforderlich den Index bis
zum Blattknoten zu durchlaufen, um das Offset zu berechnen und die
Schlüssel rekonstruieren zu können. Die nächsten Abschnitte setzen kodierte Schlüssel in den Blättern voraus. Die unkodierte Variante ist dabei
impliziert.
A.3.1
Einfügen von Schlüsseln
Neue Schlüssel werden in den Blättern eingefügt. Im Falle eines Aufspaltens muss ein modifizierter Split durchgeführt werden: Die zweite Hälfte
des überlaufenden Knotens wird in einen neuen Knoten kopiert. Der
neue Schlüssel wird nach seinem Betrag in diese Blätter eingeordnet. Da
bei einem B+ Baum Separatoren des Index lediglich als Wegweiser dienen, kann das arithmetische Mittel bestehend aus dem größten Schlüssel
39
APPENDIX A.3. DER DELTA-KODIERTE B+ BAUM
des linken Knotens und dem kleinsten Schlüssel des rechten Knotens im
Elternknoten eingebunden werden. Abschließend wird der rechte Blattknoten bezüglich dieses lokalen Offsets neu kodiert.
Im Inneren des Index werden Split und Einfügerotationen genau wie in
Abschnitt 3.1.3 und 3.1.4 für innere Knoten ausgeführt. Eine Rotation
über Blätter entspricht dem dort vorgestellten Algorithmus, mit dem
Unterschied der Separatorberechnung über das arithmetische Mittel.
A.3.2
Löschen von Schlüsseln
Bei einem B+ Baum müssen Schlüssel ausschließlich aus den Blättern
entfernt werden. Im Falle eines Unterlaufs kann, anders als beim in Abschnitt 3.1.5 beschriebenen Merge-Algorithmus, der Separator aus dem
Elternknoten direkt entfernt werden (er wird nicht in das resultierende
Blatt eingefügt).
Eine Rotation in den Blättern wird analog zu dem in Abschnitt 3.1.4
vorgestellten Verfahren durchgeführt.
Bei Entfernen von Werten aus dem Index wird eine Reparatur mit Hilfe von Rotationen oder Verschmelzung analog zum Delta-kodierten BBaum durchgeführt. Die große Rotation entfällt beim B+ Baum.
40
Literaturverzeichnis
[ADHW99] A. Ailamaki, D. J. DeWitt, M. D. Hill and D. A. Wood:
DBMSs on a Modern Processor: Where Does Time Go?,
Proceedings of the 25th International Conference on Very
Large Data Bases, VLDB ’99, Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, pages 266–277, URL
http://doi.acm.org/10.1145/645925.671662.
[BHF09]
C. Binnig, S. Hildenbrand and F. Färber: Dictionary-based
Order-preserving String Compression for Main Memory Column Stores, Proceedings of the 2009 ACM SIGMOD International Conference on Management of data, SIGMOD
’09, ACM, New York, NY, USA, pages 283–296, URL http:
//doi.acm.org/10.1145/1559845.1559877.
[BM70]
R. Bayer and E. M. McCreight: Organization and Maintenance of Large Ordered Indices, Proceedings of the 1970
ACM SIGFIDET (now SIGMOD) Workshop on Data Description, Access and Control, SIGFIDET ’70, ACM, New
York, NY, USA, pages 107–141, URL http://doi.acm.org/
10.1145/1734663.1734671.
[BMR01]
P. Bohannon, P. McIlroy and R. Rastogi: Main-Memory Index Structures with Fixed-Size Partial Keys, SIGMOD Rec.,
volume 30(2), (2001), pages 163–174, URL http://doi.
acm.org/10.1145/376284.375681.
[BU77]
R. Bayer and K. Unterauer: Prefix B-trees, ACM Transactions on Database Systems (TODS), volume 2(1), (1977),
pages 11–26.
[CGM01]
S. Chen, P. B. Gibbons and T. C. Mowry: Improving Index Performance through Prefetching, SIGMOD Rec., volume 30(2), (2001), pages 235–246, URL http://doi.acm.
org/10.1145/376284.375688.
41
APPENDIX LITERATURVERZEICHNIS
[Com79]
D. Comer: Ubiquitous B-Tree, ACM Comput. Surv., volume 11(2), (1979), pages 121–137, URL http://doi.acm.
org/10.1145/356770.356776.
[HP03]
R. A. Hankins and J. M. Patel: Effect of Node Size on
the Performance of Cache-Conscious B+-Trees, ACM SIGMETRICS Performance Evaluation Review, volume 31(1),
(2003), pages 283–294.
[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
Data Bases, VLDB ’86, Morgan Kaufmann Publishers Inc.,
San Francisco, CA, USA, pages 294–303.
[LLSL10]
I.-h. Lee, J.-w. Lee, J. Shim and S.-g. Lee: Cache Conscious
Trees on Modern Microprocessors, Proceedings of the 4th International Conference on Uniquitous Information Management and Communication, ICUIMC 2010, ACM, New York,
NY, USA, pages 43:1–43:5, URL http://doi.acm.org/10.
1145/2108616.2108668.
[LRSC01]
C. E. Leiserson, R. L. Rivest, C. Stein and T. H. Cormen: Introduction to Algorithms, The MIT press, 2nd edition, 2001.
[RR99]
J. Rao and K. A. Ross: Cache Conscious Indexing for
Decision-Support in Main Memory, Proceedings of the 25th
International Conference on Very Large Data Bases, VLDB
’99, Morgan Kaufmann Publishers Inc., San Francisco, CA,
USA, pages 78–89, URL http://doi.acm.org/10.1145/
342009.335449.
[RR00]
J. Rao and K. A. Ross: Making B+- Trees Cache Conscious
in Main Memory, SIGMOD Rec., volume 29(2), (2000), pages 475–486, URL http://doi.acm.org/10.1145/335191.
335449.
42
Herunterladen