17.11.2014 Aufrufe

Performanceoptimierung der Datenanalyse in Netzwerkgraphen durch

Performanceoptimierung der Datenanalyse in Netzwerkgraphen durch

Performanceoptimierung der Datenanalyse in Netzwerkgraphen durch

MEHR ANZEIGEN
WENIGER ANZEIGEN

Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.

YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.

<strong>Performanceoptimierung</strong> <strong>der</strong><br />

<strong>Datenanalyse</strong> <strong>in</strong> <strong>Netzwerkgraphen</strong><br />

<strong>durch</strong> Verwendung von User Def<strong>in</strong>ed<br />

Functions <strong>in</strong> e<strong>in</strong>em<br />

Datenbankmanagementsystem<br />

Diplomarbeit<br />

im Fachgebiet Informatik<br />

vorgelegt von: Andreas Redmer<br />

Studienbereich: Datenbanken und Informationssysteme<br />

Matrikelnummer: 5206419<br />

Erstgutachter: Prof. Clemens Cap<br />

Zweitgutachter: Dr. Holger Meyer<br />

Betreuer: Dr. Thomas Mundt


Zusammenfassung<br />

In L<strong>in</strong>k-State Rechnernetzen ist es üblich, dass je<strong>der</strong> Knoten die Topologie des<br />

gesamten Netzwerkes kennt und auf dessen Basis die Rout<strong>in</strong>g-Entscheidungen<br />

treffen kann. Um die Performance und Qualität des Netzwerks zu erhöhen ist<br />

meist e<strong>in</strong>e <strong>Datenanalyse</strong> notwendig. Dabei werden beispielsweise Knoten und<br />

Verb<strong>in</strong>dungen gefunden, die e<strong>in</strong>e hohe Wichtigkeit für das gesamte Netzwerk<br />

haben. Durch die regelmäßige Aufzeichnung <strong>der</strong> Topologie<strong>in</strong>formationen an<br />

e<strong>in</strong>er Stelle im Netzwerk kann e<strong>in</strong> Datenbestand geschaffen werden, <strong>der</strong> bei<br />

geeigneter Analyse Rückschlüsse auf Schwachstellen im Netzwerk geben kann.<br />

Aufgrund <strong>der</strong> großen Menge an Daten kann die <strong>Datenanalyse</strong> sehr viel Zeit <strong>in</strong><br />

Anspruch nehmen, was die Nützlichkeit ihrer Ergebnisse <strong>in</strong> Frage stellen kann.<br />

Deshalb wurde <strong>in</strong> e<strong>in</strong>er Publikation von Mundt und Vetterick [22] im Juli 2011<br />

die Rechenleistung mittels Cloud Comput<strong>in</strong>g verstärkt und <strong>der</strong> Zeitaufwand<br />

somit verr<strong>in</strong>gert. Lei<strong>der</strong> hatte diese Methode auch Nachteile, wie beispielsweise<br />

den teuren Upload <strong>der</strong> großen Datenmengen <strong>in</strong> die Cloud.<br />

In dieser Arbeit wurde für den selben Datenbestand die Performance erhöht,<br />

<strong>in</strong>dem User Def<strong>in</strong>ed Functions (UDF) <strong>in</strong> e<strong>in</strong>em Datenbankmanagementsystem<br />

e<strong>in</strong>gesetzt wurden. Die Daten werden direkt auf dem Datenbankserver analysiert<br />

und die Ergebnisse mit SQL abgefragt. Gleichzeitig wurde die bestehende<br />

Implementierung untersucht und ihre Komplexität verr<strong>in</strong>gert. Im Ergebnis<br />

konnte die Analyse nicht nur schneller, son<strong>der</strong>n auch komfortabler für den Anwen<strong>der</strong><br />

<strong>durch</strong>geführt werden. Viele Arten <strong>der</strong> <strong>Datenanalyse</strong> <strong>der</strong> Netzwerktopologiedaten<br />

können nun mit SQL ohne zusätzliche Programme <strong>durch</strong>geführt<br />

werden. Am Ende <strong>der</strong> Arbeit werden mehrere Beispiele für Datenanfragen aufgeführt,<br />

die den E<strong>in</strong>satz <strong>der</strong> neuen Funktionen zeigen und H<strong>in</strong>weise zur Laufzeit<br />

geben.<br />

© Andreas Redmer — 29. September 2011


Abstract<br />

In l<strong>in</strong>k-state computer networks it is usual that every node knows the topology<br />

of the entire network and can make the rout<strong>in</strong>g decisions based on that. To<br />

enhance the performance and quality of the network a data analysis is needed<br />

mostly. For <strong>in</strong>stance nodes and connections with a high importance for the<br />

network can be found by do<strong>in</strong>g that. By captur<strong>in</strong>g and record<strong>in</strong>g the topology<br />

<strong>in</strong>formation periodically a database can be created, which can be used to draw<br />

conclusions on weaknesses <strong>in</strong> the network after adequate data analysis. Due to<br />

the huge amount of data, the data analysis can take a lot of time, which can<br />

be question<strong>in</strong>g the utility of the results. Because of that Mundt and Vetterick<br />

presented a paper ([22]) <strong>in</strong> July 2011, which <strong>in</strong>troduced a possibility to <strong>in</strong>crease<br />

the process<strong>in</strong>g power and reduce the process<strong>in</strong>g time by cloud comput<strong>in</strong>g.<br />

Unfortunately this method had some disadvantages like the expensive upload<br />

of the data <strong>in</strong>to the cloud.<br />

In this diploma thesis the performance has been <strong>in</strong>creased by us<strong>in</strong>g user<br />

def<strong>in</strong>ed functions (UDF) <strong>in</strong> a database management system on the same data<br />

source. So the data is directly analyzed on the database server and the results<br />

are queryable by SQL. Additionally the exist<strong>in</strong>g implementation was analyzed<br />

and the time complexity was reduced. As a result, the analysis is not only<br />

faster but also more comfortable. Different k<strong>in</strong>ds of data analysis of netzwork<br />

topology data can now be accomplished with SQL and without additional<br />

programs. At the end of diploma this thesis several examples of queries are<br />

<strong>in</strong>troduced, to show the range of application and to <strong>in</strong>dicate the runtime.<br />

© Andreas Redmer — 29. September 2011


Inhaltsverzeichnis<br />

Inhaltsverzeichnis<br />

Abbildungsverzeichnis<br />

Tabellenverzeichnis<br />

Verzeichnis <strong>der</strong> List<strong>in</strong>gs<br />

III<br />

IV<br />

V<br />

1. E<strong>in</strong>leitung 1<br />

1.1. Aufbau <strong>der</strong> Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . 1<br />

1.2. Beschreibung des Mesh-Netzwerks als Graph . . . . . . . . . . . 2<br />

1.3. Qualität <strong>der</strong> zu analysierenden Daten . . . . . . . . . . . . . . . 3<br />

1.4. Ziel <strong>der</strong> Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . 7<br />

1.5. Vorausgesetzte Hard- und Software . . . . . . . . . . . . . . . . 10<br />

2. Stand <strong>der</strong> Technik 12<br />

2.1. Rout<strong>in</strong>g-Algorithmen . . . . . . . . . . . . . . . . . . . . . . . . 12<br />

2.2. Metriken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13<br />

2.3. Betrachtung <strong>der</strong> bestehenden Implementierung als Cloud-Service 14<br />

2.3.1. Algorithmische Komplexität . . . . . . . . . . . . . . . . 15<br />

2.3.2. Vor- und Nachteile <strong>der</strong> Cloudlösung . . . . . . . . . . . . 19<br />

3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung 21<br />

3.1. Wahl des DBMS und <strong>der</strong> Programmiersprache . . . . . . . . . . 21<br />

3.1.1. Wahl des Datenbankmodells . . . . . . . . . . . . . . . . 21<br />

3.1.2. Wahl des DBMS . . . . . . . . . . . . . . . . . . . . . . 26<br />

3.1.3. Wahl <strong>der</strong> Programmiersprache . . . . . . . . . . . . . . . 28<br />

3.2. Schnittstellendef<strong>in</strong>ition . . . . . . . . . . . . . . . . . . . . . . . 32<br />

3.3. Möglichkeiten <strong>der</strong> Performancemessung . . . . . . . . . . . . . . 36<br />

4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung 40<br />

4.1. Algorithmische Optimierungen . . . . . . . . . . . . . . . . . . . 40<br />

4.1.1. Optimierung des Dijkstra-Algorithmus . . . . . . . . . . 40<br />

4.1.2. Optimierung des Graphen . . . . . . . . . . . . . . . . . 42<br />

4.1.3. Die General-Gateway-Strategie“ . . . . . . . . . . . . . 43<br />

”<br />

4.1.4. Nutzung stabiler Teilergebnisse bei ähnlichen Graphen . 46<br />

© Andreas Redmer — 29. September 2011 I


Inhaltsverzeichnis<br />

4.2. Performanceoptimierter Programmierstil . . . . . . . . . . . . . 51<br />

4.2.1. Quellcodedesign . . . . . . . . . . . . . . . . . . . . . . . 51<br />

4.2.2. Zusammenhang zur algorithmischen Komplexität . . . . 53<br />

4.2.3. Design Pattern . . . . . . . . . . . . . . . . . . . . . . . 54<br />

4.2.4. Implementierung von unendlich“ . . . . . . . . . . . . . 55<br />

”<br />

4.2.5. Adjazenzmatrix statt Adjazenzliste . . . . . . . . . . . . 56<br />

4.3. Parallelisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . 57<br />

4.3.1. Multithreaded Dijkstra . . . . . . . . . . . . . . . . . . . 57<br />

4.3.2. Dijkstra auf <strong>der</strong> GPU . . . . . . . . . . . . . . . . . . . . 63<br />

4.4. Zusammenfassung und Ergebnisse . . . . . . . . . . . . . . . . . 65<br />

5. Testläufe - Beispiele für Datenabfragen 68<br />

5.1. Alle Routen zu allen Zeitpunkten . . . . . . . . . . . . . . . . . 70<br />

5.2. Routenän<strong>der</strong>ungen zwischen zwei Zeitpunkten . . . . . . . . . . 71<br />

5.3. Routenän<strong>der</strong>ungen bei Ausfall e<strong>in</strong>es Knotens . . . . . . . . . . . 73<br />

5.4. Routenän<strong>der</strong>ungen bei Ausfall zweier Knoten . . . . . . . . . . 76<br />

5.5. Knoten die häufig auf Routen liegen . . . . . . . . . . . . . . . . 79<br />

5.6. Wichtige Knoten und Kanten . . . . . . . . . . . . . . . . . . . 82<br />

5.7. Routenän<strong>der</strong>ungen bei Ausfall e<strong>in</strong>er Kante . . . . . . . . . . . . 84<br />

5.8. Suche nach Flaschenhälsen . . . . . . . . . . . . . . . . . . . . . 87<br />

6. Zusammenfassung und Ausblick 90<br />

6.1. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . 90<br />

6.2. Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91<br />

Literaturverzeichnis 94<br />

A. Anhang: SQL Anfragen i<br />

A.1. Anzahl neuer Datensätze pro M<strong>in</strong>ute . . . . . . . . . . . . . . . i<br />

A.2. Prüfung <strong>der</strong> Vollständigkeit <strong>der</strong> Daten . . . . . . . . . . . . . . ii<br />

A.3. Prüfung <strong>der</strong> Korrektheit <strong>der</strong> Daten . . . . . . . . . . . . . . . . iv<br />

A.4. Maximale Knotenanzahl auf kürzesten Pfaden . . . . . . . . . . v<br />

A.5. Floyd-Warshall-Berechnung <strong>in</strong> SQL . . . . . . . . . . . . . . . . vii<br />

A.6. Floyd-Warshall-Berechnung mit PL/Python . . . . . . . . . . . viii<br />

A.7. Test <strong>der</strong> General-Gateway-Strategie . . . . . . . . . . . . . . . . ix<br />

A.8. Implementierung <strong>der</strong> Algebra aus Abschnitt 4.1.4 . . . . . . . . x<br />

B. Anhang: Suche nach e<strong>in</strong>er Partitionierung xi<br />

© Andreas Redmer — 29. September 2011 II


Abbildungsverzeichnis<br />

Abbildungsverzeichnis<br />

3.1. Objektorientierte Speicherung <strong>der</strong> Daten (UML-Klassendiagramm) 23<br />

3.2. Integration e<strong>in</strong>er Java UDF <strong>in</strong> PostgreSQL . . . . . . . . . . . . 32<br />

3.3. Ablauf <strong>der</strong> shortestPaths Funktion . . . . . . . . . . . . . . . 33<br />

3.4. Interface für shortestPaths PL/Java (UML-Klassendiagramm) 36<br />

4.1. E<strong>in</strong> beispielhafter unmodifizierter Graph . . . . . . . . . . . . . 43<br />

4.2. E<strong>in</strong> Netzwerkgraph mit generalisiertem Gateway . . . . . . . . . 44<br />

4.3. Venn-Diagramm für zwei Dijkstra-Ergebnismengen . . . . . . . 47<br />

4.4. Schematische Darstellung - e<strong>in</strong>e vollständige Speicherung alle 10<br />

M<strong>in</strong>uten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49<br />

4.5. Schematische Darstellung - e<strong>in</strong>e vollständige Speicherung pro<br />

M<strong>in</strong>ute im Wochentag . . . . . . . . . . . . . . . . . . . . . . . 50<br />

4.6. Balkendiagramm <strong>der</strong> Ausführungszeit <strong>der</strong> UDF shortestPaths 65<br />

4.7. Reihenfolge <strong>der</strong> <strong>durch</strong>geführten Optimierungen . . . . . . . . . . 66<br />

5.1. ER-Modell <strong>der</strong> verwendeten Datenbank . . . . . . . . . . . . . . 68<br />

5.2. Graph <strong>der</strong> Kanten aus Tabelle 5.4 . . . . . . . . . . . . . . . . . 83<br />

5.3. Graph aller Kanten aus Tabelle 5.5 . . . . . . . . . . . . . . . . 86<br />

A.1. E<strong>in</strong> kartesisches E<strong>in</strong>heitsgitter . . . . . . . . . . . . . . . . . . .<br />

vi<br />

B.1. Beispielgraph: Alle kürzesten Wege <strong>in</strong> die Menge <strong>der</strong> Backbones xii<br />

B.2. Beispielgraph: Alle kürzesten Wege zu den Gateways ohne Beachtung<br />

<strong>der</strong> Partitionen . . . . . . . . . . . . . . . . . . . . . . xiii<br />

B.3. Beispielgraph: Alle kürzesten Wege zu den Gateways unter Beachtung<br />

<strong>der</strong> Partitionen . . . . . . . . . . . . . . . . . . . . . . xiv<br />

B.4. Optimale Partitionen am 14.09.2010 um 14 Uhr . . . . . . . . . xvi<br />

© Andreas Redmer — 29. September 2011 III


Tabellenverzeichnis<br />

Tabellenverzeichnis<br />

1.1. Format <strong>der</strong> aufgezeichneten Daten . . . . . . . . . . . . . . . . . 3<br />

1.2. Allgeme<strong>in</strong>e Übersicht über die betrachteten Daten . . . . . . . . 4<br />

1.3. Constra<strong>in</strong>ts für Vollständigkeit . . . . . . . . . . . . . . . . . . . 5<br />

1.4. Constra<strong>in</strong>ts für Exaktheit . . . . . . . . . . . . . . . . . . . . . 6<br />

1.5. Zusammenfassung <strong>der</strong> Problemklassen . . . . . . . . . . . . . . 10<br />

1.6. Spezifikation <strong>der</strong> Testsysteme . . . . . . . . . . . . . . . . . . . 11<br />

3.1. Laufzeiten des Floyd-Warshall-Algorithmus . . . . . . . . . . . . 29<br />

4.1. Ausführungszeiten bei gleichzeitiger Ausführung mehrerer Dijkstra<br />

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63<br />

4.2. Ausführungszeiten und Verbesserungen <strong>der</strong> Optimierungen . . . 67<br />

5.1. Erste Ergebnisse <strong>der</strong> SQL-Abfrage <strong>in</strong> List<strong>in</strong>g 5.3 . . . . . . . . . 74<br />

5.2. Die Ergebnisse <strong>der</strong> SQL-Abfrage <strong>in</strong> List<strong>in</strong>g 5.4 . . . . . . . . . . 77<br />

5.3. Die Ergebnisse <strong>der</strong> SQL-Abfrage <strong>in</strong> List<strong>in</strong>g 5.5 . . . . . . . . . . 80<br />

5.4. Die Ergebnisse <strong>der</strong> SQL-Abfragen <strong>in</strong> List<strong>in</strong>g 5.6 . . . . . . . . . 82<br />

5.5. Das Ergebnis <strong>der</strong> SQL-Abfrage <strong>in</strong> List<strong>in</strong>g 5.7 . . . . . . . . . . . 85<br />

5.6. Das Ergebnis <strong>der</strong> SQL-Abfrage <strong>in</strong> List<strong>in</strong>g 5.8 . . . . . . . . . . . 88<br />

© Andreas Redmer — 29. September 2011 IV


Verzeichnis <strong>der</strong> List<strong>in</strong>gs<br />

Verzeichnis <strong>der</strong> List<strong>in</strong>gs<br />

2.1. Pseudocode des Algorithmus aus [22] . . . . . . . . . . . . . . . 16<br />

2.2. Dijkstra-Algorithmus <strong>in</strong> Pseudocode . . . . . . . . . . . . . . . . 18<br />

2.3. Komplexität des Dijkstra-Algorithmus . . . . . . . . . . . . . . 18<br />

2.4. <strong>in</strong>it-Funktion des Dijkstra-Algorithmus . . . . . . . . . . . . . 18<br />

2.5. update-Funktion des Dijkstra-Algorithmus . . . . . . . . . . . . 18<br />

3.1. Typendef<strong>in</strong>itionen <strong>in</strong> PostreSQL . . . . . . . . . . . . . . . . . . 24<br />

3.2. JDBC-Verb<strong>in</strong>dung zu e<strong>in</strong>em MySQL-Server <strong>in</strong> Java . . . . . . . 27<br />

3.3. JDBC-Verb<strong>in</strong>dung zu e<strong>in</strong>em PostgreSQL-Server <strong>in</strong> Java . . . . . 27<br />

3.4. Floyd-Warshall-Algorithmus <strong>in</strong> Pseudocode . . . . . . . . . . . . 29<br />

3.5. Zeitmessung für e<strong>in</strong> Java-Programm . . . . . . . . . . . . . . . . 39<br />

4.1. Dijkstra-Algorithmus <strong>in</strong> Pseudocode mit Möglichkeiten zur Parallelisierung<br />

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57<br />

4.2. Zwei Anfragen die getrennt gesendet werden können . . . . . . . 60<br />

4.3. shortestPaths S<strong>in</strong>glethread Pseudocode . . . . . . . . . . . . . . 61<br />

4.4. shortestPaths Doublethread Pseudocode . . . . . . . . . . . . . 61<br />

4.5. Ablauf des CUDA-Programmes <strong>in</strong> Pseudocode . . . . . . . . . . 64<br />

5.1. Abfrage aller Routen . . . . . . . . . . . . . . . . . . . . . . . . 70<br />

5.2. Abfrage aller Routenän<strong>der</strong>ungen an e<strong>in</strong>em Tag . . . . . . . . . . 71<br />

5.3. Abfrage <strong>der</strong> Routenän<strong>der</strong>ungen beim Ausfall e<strong>in</strong>es Knotens . . . 73<br />

5.4. Abfrage <strong>der</strong> Routenän<strong>der</strong>ungen beim Ausfall zweier Knoten . . 76<br />

5.5. Abfrage wie oft e<strong>in</strong> Knoten auf e<strong>in</strong>er Route liegt . . . . . . . . . 79<br />

5.6. Abfragen für Wichtigkeit von Knoten und Kanten . . . . . . . . 82<br />

5.7. Abfrage nach Routenän<strong>der</strong>ungen <strong>durch</strong> Wegfall e<strong>in</strong>er Kante . . 84<br />

5.8. Abfrage <strong>der</strong> Kanten die häufig auf <strong>der</strong> Route e<strong>in</strong>es Knotens liegen 87<br />

A.1. Abfrage <strong>der</strong> Anzahl neuer Datensätze pro M<strong>in</strong>ute . . . . . . . . i<br />

A.2. Abfrage <strong>der</strong> Vollständigkeit <strong>der</strong> Daten . . . . . . . . . . . . . . ii<br />

A.3. Abfrage <strong>der</strong> Lücken <strong>in</strong> <strong>der</strong> Datenaufzeichnung . . . . . . . . . . iii<br />

A.4. Abfrage <strong>der</strong> Exaktheit <strong>der</strong> Daten . . . . . . . . . . . . . . . . . iv<br />

A.5. Abfrage <strong>der</strong> Durchschnittlichen und maximalen Pfadlänge <strong>der</strong><br />

kürzesten Pfade . . . . . . . . . . . . . . . . . . . . . . . . . . . v<br />

© Andreas Redmer — 29. September 2011 V


Verzeichnis <strong>der</strong> List<strong>in</strong>gs<br />

A.6. Floyd-Warshall Berechnung <strong>in</strong> SQL . . . . . . . . . . . . . . . . vii<br />

A.7. Floyd-Warshall Berechnung mit PL/Python . . . . . . . . . . . viii<br />

A.8. Anzahl Unterschiede zwischen herkömmlicher und General Gateway<br />

Strategie . . . . . . . . . . . . . . . . . . . . . . . . . . . ix<br />

A.9. Die Algebra aus Abschnitt 4.1.4 <strong>in</strong> PL/pgSQL . . . . . . . . . . x<br />

© Andreas Redmer — 29. September 2011 VI


1. E<strong>in</strong>leitung<br />

1. E<strong>in</strong>leitung<br />

Das Rostocker Opennet [24] ist e<strong>in</strong> komplexes Wifi-Mesh-Netzwerk mit zur<br />

Zeit etwa 200 Knoten und mehreren Gateways <strong>in</strong>s Internet. Die Mitglie<strong>der</strong><br />

des Opennet e.V. verwenden WLAN-Technik um sich von Dach zu Dach zu<br />

vernetzen. Die Topologie<strong>in</strong>formationen des Netzwerks s<strong>in</strong>d <strong>in</strong> allen Knoten vorhanden.<br />

An e<strong>in</strong>er zentralen Stelle im Netzwerk werden die Topology Control<br />

Nachrichten e<strong>in</strong>mal pro M<strong>in</strong>ute aufgezeichnet. Diese Aufzeichnungen s<strong>in</strong>d für<br />

die Funktion des Netzwerkes nicht notwendig, son<strong>der</strong>n nur für nachträgliche<br />

Analysen. Anhand dieser Daten lassen sich die Routen, die zu e<strong>in</strong>em bestimmten<br />

Zeitpunkt gültig waren, berechnen. Diese wie<strong>der</strong>um können für verschiedene<br />

Arten <strong>der</strong> Netzwerkanalyse genutzt werden. Derzeit dauert die Berechnung<br />

<strong>der</strong> Routen für e<strong>in</strong>en Zeitpunkt ca. vier Sekunden. Bei <strong>der</strong> Analyse von mehreren<br />

Wochen o<strong>der</strong> Monaten aufgezeichneter Daten wird <strong>der</strong> Zeitaufwand sehr<br />

hoch. Deshalb wurde <strong>in</strong> e<strong>in</strong>er Publikation von Mundt und Vetterick [22] die<br />

Rechenleistung mittels Cloud Comput<strong>in</strong>g verstärkt und <strong>der</strong> Zeitaufwand somit<br />

verr<strong>in</strong>gert. Dieser Ansatz hat jedoch auch e<strong>in</strong>ige Nachteile. Im Rahmen dieser<br />

Arbeit soll versucht werden die Performance zu erhöhen <strong>in</strong>dem User Def<strong>in</strong>ed<br />

Functions (UDF) <strong>in</strong> e<strong>in</strong>em Datenbankmanagementsystem (DBMS)<br />

e<strong>in</strong>gesetzt werden. Die Routen sollen direkt auf dem Datenbankserver berechnet<br />

und später mittels SQL direkt abgefragt werden. Weiterh<strong>in</strong> werden im<br />

Rahmen dieser Arbeit die vorhandenen Quelltexte untersucht und eventuelle<br />

Schwachstellen <strong>in</strong> <strong>der</strong> Performance beseitigt.<br />

1.1. Aufbau <strong>der</strong> Arbeit<br />

Im ersten Kapitel dieser Arbeit werden die graphentheoretischen Grundlagen<br />

für das Mesh-Netzwerk erklärt. Diese s<strong>in</strong>d notwendig um zu verstehen wie die<br />

<strong>Datenanalyse</strong> funktioniert. Weiterh<strong>in</strong> wird die Herkunft, <strong>der</strong> Aufbau und die<br />

Qualität <strong>der</strong> zu analysierenden Daten beschrieben. Ebenfalls wird das Ziel <strong>der</strong><br />

Arbeit genau beschrieben und die zu lösenden Probleme (und Problemklassen)<br />

erklärt.<br />

Im Kapitel 2 werden bestehende Rout<strong>in</strong>galgorithmen und dazu gehörige Metriken<br />

beschrieben. Weiterh<strong>in</strong> wird die bestehende Implementierung für die<br />

© Andreas Redmer — 29. September 2011 1


1. E<strong>in</strong>leitung<br />

<strong>Datenanalyse</strong> <strong>in</strong> <strong>der</strong> Cloud genauer betrachtet. Dabei wird e<strong>in</strong>e Komplexitätsbetrachtung<br />

<strong>durch</strong>geführt. Anschließend werden die Vor- und Nachteile <strong>der</strong><br />

bestehenden Lösung erörtert.<br />

Im Kapitel 3 wird das, im Rahmen dieser Arbeit erstellte, Konzept zur Beschleunigung<br />

<strong>der</strong> <strong>Datenanalyse</strong> erklärt. Dabei werden auch viele Alternativen<br />

erwähnt und es wird erklärt warum genau <strong>der</strong> gewählte Lösungsweg e<strong>in</strong>geschlagen<br />

wurde. Insbeson<strong>der</strong>e bef<strong>in</strong>det sich <strong>in</strong> diesem Kapitel auch die Def<strong>in</strong>ition<br />

<strong>der</strong> Schnittstelle, die vor <strong>der</strong> Implementierung spezifiziert wurde. Diese kann<br />

als Dokumentation für die Nutzung <strong>der</strong> erstellen UDF verwendet werden. Weiterh<strong>in</strong><br />

werden e<strong>in</strong>ige Konzepte zur Zeitmessung erklärt, um verständlich zu<br />

machen, wie die Erhöhung <strong>der</strong> Performance überhaupt gemessen werden kann.<br />

Das entstandene Konzept lässt sich auf verschiedene Arten implementieren.<br />

Im Kapitel 4 wird erklärt, wie die Implementierung angefertigt wurde um die<br />

Performance zu erhöhen und dabei die Usability möglichst nicht zu verr<strong>in</strong>gern.<br />

In diesem Kapitel wird das Wort ”<br />

Optimierung“ als Synonym für ”<br />

Verbesserung<br />

<strong>der</strong> Performance“ o<strong>der</strong> ”<br />

Verr<strong>in</strong>gerung <strong>der</strong> Laufzeit“ verwendet werden. Es<br />

werden sehr verschiedene Arten <strong>der</strong> Optimierung aufgeführt.<br />

Die letzten beiden Kapitel beschreiben beispielhaft die Verwendung <strong>der</strong> entwickelten<br />

UDF und vermitteln e<strong>in</strong>e Vorstellung über die zukünftigen E<strong>in</strong>satzmöglichkeiten.<br />

Dabei werden auch erste <strong>in</strong>teressante Ergebnisse kle<strong>in</strong>erer <strong>Datenanalyse</strong>n<br />

präsentiert. Abschließend wird beschrieben, welche Vor- und Nachteile<br />

sich aus dieser Arbeit ergeben, welche Verbesserungen geschafft wurden<br />

und welche weiteren Anwendungen darauf basierend möglich s<strong>in</strong>d.<br />

1.2. Beschreibung des Mesh-Netzwerks als Graph<br />

Im Rahmen dieser Arbeit wird das Mesh-Netzwerk als mathematischer Graph<br />

betrachtet. E<strong>in</strong> Graph (G) besteht aus e<strong>in</strong>er Menge von Knoten (V ) und e<strong>in</strong>er<br />

Menge von Kanten (E). Die Kanten s<strong>in</strong>d <strong>in</strong> dieser Arbeit gerichtete Kanten<br />

und somit mathematische Tupel <strong>der</strong> Form<br />

(a, b) ∈ E : a ∈ V ∧ b ∈ V.<br />

Für die bessere Lesbarkeit sollen im Rahmen dieser Arbeit für die Anzahl<br />

<strong>der</strong> Kanten und Knoten folgende Substitutionen gelten:<br />

|V | = n<br />

|E| = m<br />

© Andreas Redmer — 29. September 2011 2


1. E<strong>in</strong>leitung<br />

In diesem Fall s<strong>in</strong>d die Netzwerkknoten die Knoten des Graphs. Die direkten<br />

WLAN-Verb<strong>in</strong>dungen zwischen ihnen s<strong>in</strong>d die Kanten. Für das Rout<strong>in</strong>g<br />

müssen die Kanten gewichtet se<strong>in</strong>. Für das Gewicht <strong>der</strong> Kanten wird e<strong>in</strong>e Metrik<br />

verwendet, welche die Qualität <strong>der</strong> Verb<strong>in</strong>dung beschreibt. Dabei ist zu<br />

beachten, dass e<strong>in</strong> Qualitätsmaß aus mathematischer Sicht e<strong>in</strong>e Ähnlichkeitsfunktion<br />

ist, bei <strong>der</strong> e<strong>in</strong> hoher Wert für gute Qualität und e<strong>in</strong> niedriger Wert<br />

für schlechte Qualität steht. Die <strong>in</strong> dieser Arbeit betrachteten Daten haben als<br />

höchsten Wert für die Qualität 1 und als niedrigsten Wert 0. Dabei bedeutet 0,<br />

dass praktisch ke<strong>in</strong>e Verb<strong>in</strong>dung vorhanden ist. Da beim Rout<strong>in</strong>g die kürzesten<br />

Wege gesucht werden, muss diese Funktion <strong>in</strong> e<strong>in</strong>e Abstandsfunktion (Metrik)<br />

abgeän<strong>der</strong>t werden. Wenn f(x) die Ähnlichkeitsfunktion ist, dann wären 1<br />

f(x)<br />

o<strong>der</strong> 1 − f(x) mögliche Abstandsfunktionen. Da<strong>durch</strong> ist sichergestellt, dass<br />

die Verb<strong>in</strong>dungen mit <strong>der</strong> höchsten Qualität als kle<strong>in</strong>e“ Kanten dargestellt<br />

”<br />

werden. Die Berechnung des kürzesten Weges <strong>in</strong> dem Graphen ist somit gleich<br />

<strong>der</strong> Ermittlung <strong>der</strong> besten“ Route.<br />

”<br />

1.3. Qualität <strong>der</strong> zu analysierenden Daten<br />

Wie bei L<strong>in</strong>k-State-Verfahren üblich s<strong>in</strong>d die Topologie<strong>in</strong>formationen über das<br />

Netzwerk <strong>in</strong> allen Knoten vorhanden. An e<strong>in</strong>er zentralen Stelle im Netzwerk<br />

werden die von OLSR 1 def<strong>in</strong>ierten und regelmäßig von allen Knoten versendeten<br />

TC (Topology Control) Nachrichten e<strong>in</strong>mal pro M<strong>in</strong>ute aufgezeichnet. Die<br />

Tabelle 1.1 zeigt e<strong>in</strong>ige Beispieldatensätze aus <strong>der</strong> Aufzeichnung.<br />

Timestamp Node A Node B LQ NLQ<br />

2010-09-10 00:00:02 192.168.0.244 192.168.1.150 1.000 1.000<br />

2010-09-10 00:00:02 192.168.0.254 192.168.1.129 1.000 1.000<br />

2010-09-10 00:00:02 192.168.0.254 192.168.1.14 0.349 0.957<br />

2010-09-10 00:00:02 192.168.0.254 192.168.1.15 0.588 1.000<br />

2010-09-10 00:00:02 192.168.0.254 192.168.1.182 1.000 1.000<br />

2010-09-10 00:00:02 192.168.0.254 192.168.1.25 0.643 1.000<br />

2010-09-10 00:00:02 192.168.0.254 192.168.1.4 0.671 1.000<br />

2010-09-10 00:00:02 192.168.0.254 192.168.10.2 1.000 1.000<br />

2010-09-10 00:00:02 192.168.0.254 192.168.2.7 1.000 0.976<br />

2010-09-10 00:00:02 192.168.0.254 192.168.2.8 1.000 0.000<br />

Tabelle 1.1.: Format <strong>der</strong> aufgezeichneten Daten<br />

Je<strong>der</strong> Datensatz besteht aus e<strong>in</strong>em Timestamp, Knoten A, Knoten B, L<strong>in</strong>k<br />

Quality und Neighbour L<strong>in</strong>k Quality. Die Aufzeichnungen beg<strong>in</strong>nen mit dem<br />

1 Optimized L<strong>in</strong>k State Rout<strong>in</strong>g [6] (das im Opennet e<strong>in</strong>gesetzte Rout<strong>in</strong>gprotokoll)<br />

© Andreas Redmer — 29. September 2011 3


1. E<strong>in</strong>leitung<br />

Timestamp 07.04.2010 14:20:02 Uhr. Im Rahmen dieser Arbeit, werden nur<br />

Aufzeichnungen bis zum Timestamp 28.03.2011 17:41:02 Uhr betrachtet um<br />

genaue Angaben über Umfang, Zeitaufwände und Geschw<strong>in</strong>digkeiten machen<br />

zu können. Pro M<strong>in</strong>ute fallen <strong>durch</strong>schnittlich 852 neue Datensätze an 2 . Je<strong>der</strong><br />

Datensatz repräsentiert e<strong>in</strong>e Verb<strong>in</strong>dung im Netzwerk. Je<strong>der</strong> Timestamp<br />

besteht aus Datum und Uhrzeit (ohne Zeitzone), wobei die Systemzeit des aufzeichnenden<br />

Knotens verwendet wird. Knoten A und Knoten B enthalten die<br />

Bezeichner für die beiden Knoten <strong>der</strong> Verb<strong>in</strong>dung. Bezeichnet werden die Knoten<br />

<strong>in</strong> Form ihrer IP-Adresse <strong>in</strong> dezimaler Schreibweise. L<strong>in</strong>k Quality (LQ) ist<br />

e<strong>in</strong> Qualitätsmaß für die Verb<strong>in</strong>dung von Knoten A nach Knoten B. Da dieses<br />

Qualitätsmaß asynchron ermittelt wird, gibt es noch die Neighbour L<strong>in</strong>k<br />

Quality (NLQ), welche die Übertragungsqualität von Knoten B zu Knoten A<br />

repräsentiert. Die Qualitätswerte s<strong>in</strong>d hier Fließkommawerte im Intervall [0, 1]<br />

mit e<strong>in</strong>er Genauigkeit von vier Dezimalstellen. Dabei steht die 0 für ”<br />

ke<strong>in</strong>e<br />

Verb<strong>in</strong>dung“ und die 1 für ”<br />

sehr gute Verb<strong>in</strong>dung“. Näheres zur Bestimmung<br />

dieses Qualitätsmaßes f<strong>in</strong>det sich im Abschnitt 2.2. Die Qualität <strong>der</strong> Daten ist<br />

bezüglich <strong>der</strong> Vollständigkeit und Exaktheit jedoch unvollkommen, was für die<br />

weitere Auswertung beachtet werden muss.<br />

Um e<strong>in</strong>e Vorstellung von den Daten zu bekommen, zeigt die Tabelle 1.2 e<strong>in</strong>e<br />

allgeme<strong>in</strong>e Übersicht über die betrachteten Daten.<br />

Erster Timestamp 2010-04-07 14:20:02<br />

Letzter Timestamp 2011-03-28 17:41:02<br />

Anzahl verschiedener Timestamps 334960<br />

Anzahl verschiedener Knoten (IP Adressen) 260<br />

Anzahl Datensätze 278884776<br />

Dateigröße als CSV Export (Str<strong>in</strong>g) 15,3 GB<br />

Tabellengröße <strong>in</strong> PostgreSQL Tabelle 16,0 GB<br />

Tabelle 1.2.: Allgeme<strong>in</strong>e Übersicht über die betrachteten Daten<br />

Vollständigkeit<br />

Bevor mit den Daten gearbeitet wird, soll <strong>der</strong>en Vollständigkeit geprüft werden.<br />

Unvollständigkeiten <strong>in</strong> den Daten könnten später zu falschen Ergebnissen<br />

führen, wenn sie nicht beachtet werden. Die Tabelle 1.3 listet e<strong>in</strong>ige beispielhafte<br />

Constra<strong>in</strong>ts auf, die theoretisch alle vollständig erfüllt se<strong>in</strong> sollten. Inwiefern<br />

2 Durchschnittliche Anzahl <strong>der</strong> Datensätze pro Timestamp aus den letzten 3 Wochen <strong>der</strong><br />

Aufzeichnung (vgl. Anhang A.1).<br />

© Andreas Redmer — 29. September 2011 4


1. E<strong>in</strong>leitung<br />

sie tatsächlich erfüllt wurden, listet die Spalte ”<br />

erfüllt“ auf. Die dafür verwendeten<br />

SQL-Anfragen und <strong>der</strong>en Ergebnisse f<strong>in</strong>den sich im Anhang A.2.<br />

Constra<strong>in</strong>t Fälle korrekte Fälle erfüllt<br />

44 ≤ E<strong>in</strong>träge < 1600 334960 334829 99,96%<br />

Timestamp<br />

Timestamps<br />

Stunde<br />

= 60 5586 5570 99,71%<br />

Timestamps<br />

Tag<br />

= 1440 235 223 94,89%<br />

Timestamps<br />

Woche<br />

= 10080 35 25 71,43%<br />

Tabelle 1.3.: Constra<strong>in</strong>ts für Vollständigkeit<br />

Das Contra<strong>in</strong>t <strong>in</strong> Zeile 1 <strong>in</strong> Tabelle 1.3 prüft ob die Anzahl <strong>der</strong> m<strong>in</strong>ütlich<br />

aufgezeichneten Daten <strong>in</strong> e<strong>in</strong>em s<strong>in</strong>nvollen Rahmen liegt. Es ist sehr schwierig<br />

festzustellen, die Aufzeichnung zu e<strong>in</strong>em Zeitpunkt abgebrochen ist o<strong>der</strong><br />

tatsächlich nur sehr wenige E<strong>in</strong>träge enthält. E<strong>in</strong>fache statistische Mittel wie<br />

z. B. das Suchen von Ausreißern <strong>durch</strong> Rangbildung, führten nicht zum Erfolg.<br />

Es ist also e<strong>in</strong>e komplexere Analyse notwendig. An dieser Stelle wurde<br />

letztlich empirisch ermittelt, dass alle Aufzeichnungen, die weniger als 44 Datensätze<br />

be<strong>in</strong>halten def<strong>in</strong>itiv falsch s<strong>in</strong>d. Weiterh<strong>in</strong> wurde festgestellt, dass am<br />

31.10.2010 zwischen 2 Uhr und 3 Uhr mehr als 1600 Datensätze pro M<strong>in</strong>ute<br />

aufgezeichnet wurden, während es normalerweise nie mehr als 1000 Datensätze<br />

waren. Dies geschah aufgrund <strong>der</strong> Umstellung von Sommerzeit auf W<strong>in</strong>terzeit.<br />

In den weiteren Zeilen <strong>der</strong> Tabelle 1.3 wird die Vollständigkeit nach Stunden,<br />

Tagen und Wochen gruppiert 3 . Es s<strong>in</strong>d fast alle aufgezeichneten Stunden<br />

vollständig. Weiterh<strong>in</strong> s<strong>in</strong>d ca. 95% <strong>der</strong> aufgezeichneten Tage vollständig<br />

aufgezeichnet. Jedoch s<strong>in</strong>d nur 25 von <strong>in</strong>sgesamt 35 aufgezeichneten Wochen<br />

vollständig.<br />

Unabhängig von unvollständig aufgezeichneten Zeitpunkten gibt es auch<br />

zwischen den Zeitpunkten e<strong>in</strong>ige Lücken <strong>in</strong> den Aufzeichnungen. Die SQL-<br />

Anfragen zur Ermittlung dieser Lücken f<strong>in</strong>den sich ebenfalls im Anhang A.2.<br />

Es handelt sich als meist um kle<strong>in</strong>e Aussetzer, bei denen nur für e<strong>in</strong>ige M<strong>in</strong>uten<br />

ke<strong>in</strong>e Aufzeichnungen gemacht wurden. Allerd<strong>in</strong>gs gibt es mit mehr als<br />

108 Tagen auch e<strong>in</strong>e sehr große Lücke <strong>in</strong> den Aufzeichnungen. So wurden vom<br />

24.05.2010 bis zum 09.09.2010 ke<strong>in</strong>e Aufzeichnungen gemacht.<br />

3 Unvollständige Tage/Wochen wie z. B. <strong>der</strong>/die erste und letzte aufgezeichnete Tag/Woche<br />

wurden nicht beachtet<br />

© Andreas Redmer — 29. September 2011 5


1. E<strong>in</strong>leitung<br />

Exaktheit<br />

Die Tabelle 1.4 zeigt e<strong>in</strong>ige Constra<strong>in</strong>ts für die Exaktheit <strong>der</strong> aufgezeichneten<br />

Daten. Die erste Zeile def<strong>in</strong>iert, dass ke<strong>in</strong>e E<strong>in</strong>träge <strong>der</strong> Form LQ = LQN = 0<br />

vorhanden s<strong>in</strong>d, da dies e<strong>in</strong>e nicht vorhandene Kante wäre. Zeile 2 prüft ob alle<br />

Qualitätswerte unter 1 liegen. Die letzte Zeile zeigt, dass die Qualität asynchron<br />

zu verschiedenen Zeitpunkten und an verschiedenen Stellen im Netzwerk<br />

gemessen wird. Die Aufzeichnung <strong>der</strong> Daten im Netzwerk erfolgt m<strong>in</strong>ütlich,<br />

aber <strong>der</strong> Austausch <strong>der</strong> Topology Control Informationen zwischen den Knoten<br />

mit niedrigerer Frequenz. Somit trifft je<strong>der</strong> Knoten die Rout<strong>in</strong>gentscheidungen<br />

immer auf Basis des Netzwerkes aus se<strong>in</strong>er aktuellen Sicht. Dabei kommt<br />

es <strong>in</strong>sbeson<strong>der</strong>e beim E<strong>in</strong>- und Ausschalten von Knoten zu Ungenauigkeiten.<br />

Beispielsweise kann e<strong>in</strong>e Verb<strong>in</strong>dung die von e<strong>in</strong>em Knoten als ”<br />

sehr gut“ bewertet<br />

wurde, von dem nächsten Knoten schon als ”<br />

nicht vorhanden“ bewertet<br />

werden, weil die Messung e<strong>in</strong> paar Sekunden später erfolgte. Durch die redundanten<br />

Informationen <strong>in</strong> <strong>der</strong> Datenaufzeichung kommt es also dazu, dass die<br />

selbe Kante mit zwei verschiedenen Qualitätswerten bewertet wurde. In <strong>der</strong><br />

letzten Zeile <strong>der</strong> Tabelle 1.4 ist zu erkennen, dass <strong>in</strong> mehr als 50% <strong>der</strong> Fälle<br />

e<strong>in</strong>e Kante zum selben Zeitpunkt <strong>durch</strong> e<strong>in</strong>en an<strong>der</strong>en Datensatz an<strong>der</strong>s bewertet<br />

wurde. Da<strong>durch</strong> werden je beide Datensätze als nicht exakt angesehen.<br />

Die SQL-Anfragen, welche die Werte für Tabelle 1.4 lieferten, bef<strong>in</strong>den sich im<br />

Anhang A.3.<br />

Constra<strong>in</strong>t Fälle korrekte Fälle erfüllt<br />

lq > 0 ∨ lqn > 0 278884776 278850737 99,99%<br />

lq ≤ 1 ∧ lqn ≤ 1 278884776 278884776 100,00%<br />

∀((t, a, b, lq 1 , lqn 1 ), (t, b, a, lq 2 , lqn 2 ))<br />

=⇒ lq 1 = lqn 2 ∧ lqn 1 = lq 2<br />

278884776 131368930 47,11%<br />

Tabelle 1.4.: Constra<strong>in</strong>ts für Exaktheit<br />

In <strong>der</strong> <strong>in</strong>ternen Repräsentation des Graphen (z. B. Adjazenzliste) die später<br />

verwendet werden muss, kann man <strong>durch</strong>aus mehrere Kanten zwischen<br />

zwei Knoten e<strong>in</strong>fügen. Der Rout<strong>in</strong>galgorithmus würde automatisch immer das<br />

kle<strong>in</strong>ste <strong>der</strong> Gewichte wählen. Ebenso ist es möglich, im Rahmen e<strong>in</strong>er Vorverarbeitung<br />

<strong>der</strong> Daten, nur das M<strong>in</strong>imum dieser Daten zu speichern. Auch<br />

das Maximum, <strong>der</strong> Durchschnitt o<strong>der</strong> e<strong>in</strong>e zufällige Auswahl <strong>der</strong> möglichen<br />

Kanten ist denkbar. Davon kann ke<strong>in</strong>e Möglichkeit generell als ”<br />

richtig“ betrachtet<br />

werden. Im Rahmen dieser Arbeit wird e<strong>in</strong>e determ<strong>in</strong>istische Variante<br />

verwendet, um die Ergebnisse mit denen an<strong>der</strong>er Algorithmen vergleichbar zu<br />

machen.<br />

© Andreas Redmer — 29. September 2011 6


1. E<strong>in</strong>leitung<br />

Weiterh<strong>in</strong> gilt für den bisherigen Zeitraum, dass alle vier Gateways immer<br />

konstant anwesend waren und somit <strong>in</strong> jedem Timestamp auftauchen sollten.<br />

Daraus ergibt sich auch, dass es immer m<strong>in</strong>destens e<strong>in</strong>e Verb<strong>in</strong>dung von e<strong>in</strong>em<br />

Gateway <strong>in</strong> den Rest des Graphen geben sollte. Das ist e<strong>in</strong>e Voraussetzung<br />

um überhaupt die kürzesten Wege zu diesem Gateway berechnen zu können.<br />

Da die Gateways im Allgeme<strong>in</strong>en jedoch als variable Eigenschaft aller Daten<br />

betrachtet werden sollen, wird diese Voraussetzung an dieser Stelle noch nicht<br />

geprüft.<br />

1.4. Ziel <strong>der</strong> Arbeit<br />

Ziel dieser Arbeit ist es, die bisherigen Möglichkeiten <strong>der</strong> <strong>Datenanalyse</strong> zu beschleunigen.<br />

Dazu soll e<strong>in</strong>e Schnittstelle <strong>in</strong> Form von UDF erstellt werden, mit<br />

<strong>der</strong>en Hilfe e<strong>in</strong> Datenanalyst die beschriebenen Daten analysieren kann. Insbeson<strong>der</strong>e<br />

soll dies mit e<strong>in</strong>er besseren Performance geschehen als <strong>in</strong> bisherigen<br />

Arbeiten (z. B. <strong>in</strong> [22]). In zukünftigen Arbeiten soll die Möglichkeit bestehen,<br />

abgeleitete Kenngrößen zu bestimmen.<br />

Die verschiedenen möglichen Anfragen an die Datenbank lassen sich <strong>in</strong> bestimmte<br />

Problemklassen e<strong>in</strong>teilen, die im Folgenden aufgezählt werden. Die<br />

Aufzählung ist nicht abschließend. Es ist wichtig diese Problemklassen zu kennen,<br />

da sie das zu erreichende Ziel <strong>der</strong> Arbeit gut beschreiben.<br />

Problemklasse A: Knotenaktivität<br />

Diese Klasse be<strong>in</strong>haltet beispielsweise die An- und Abschaltaktivität von Knoten.<br />

Diese Problemklasse lässt sich mit SQL aus den Daten ermitteln ohne<br />

weitere Graphenalgorithmen auszuführen. UDF s<strong>in</strong>d für diese Anfragen nicht<br />

nötig.<br />

Problemklasse B: Routen<br />

Diese Klasse be<strong>in</strong>haltet beispielsweise die zu e<strong>in</strong>em bestimmten Zeitpunkt geltenden<br />

Routen. Auch e<strong>in</strong>e Anfrage nach <strong>der</strong> Stabilität <strong>der</strong> Routen über bestimmte<br />

Zeiträume ist vorstellbar. Dies wäre z. B. nutzbar um die Agilität des<br />

Rout<strong>in</strong>g-Protokolls zu justieren.<br />

Die Routen können mit bekannten Graphenalgorithmen zur Ermittlung kürzester<br />

Pfade von e<strong>in</strong>er Quelle (Gateway) zu allen an<strong>der</strong>en Knoten berechnet<br />

werden. Bei mehreren Gateways muss <strong>der</strong> Algorithmus mehrmals ausgeführt<br />

© Andreas Redmer — 29. September 2011 7


1. E<strong>in</strong>leitung<br />

und am Ende die ger<strong>in</strong>gste Entfernung zu e<strong>in</strong>em Gateway ausgewählt werden.<br />

Die Laufzeit des Verfahrens erhöht sich da<strong>durch</strong> um e<strong>in</strong>en konstanten Faktor.<br />

Die zugehörige graphentheoretische Problemstellung ist S<strong>in</strong>gle-Source-<br />

Shortest-Path (SSSP). Die Algorithmen Dijkstra [8] und Bellman-Ford [3]<br />

lösen dieses Problem. Dijkstra hat e<strong>in</strong>e ger<strong>in</strong>gere Zeitkomplexität. E<strong>in</strong>ige optimierte<br />

Varianten, wie z. B. <strong>der</strong> A* o<strong>der</strong> D* Algorithmus [18][29] funktionieren<br />

nur, wenn <strong>der</strong> kürzeste Weg von e<strong>in</strong>er Quelle zu e<strong>in</strong>em Ziel gesucht ist. Im<br />

Wesentlichen wird dabei versucht den Algorithmus so früh wie möglich abzubrechen,<br />

wenn <strong>der</strong> Zielknoten erreicht ist. Dabei än<strong>der</strong>t sich die Worst-Case-<br />

Komplexität nicht, jedoch werden die <strong>durch</strong>schnittlichen Laufzeiten mit diesen<br />

Algorithmen verbessert. Da jedoch für die Problemklasse B stets die kürzesten<br />

Wege zu allen an<strong>der</strong>en Knoten gesucht werden, ist Dijkstra die beste Wahl.<br />

Auch <strong>der</strong> Floyd-Warshall-Algorithmus [13], <strong>der</strong> das All-Pairs-Shortest-Path<br />

(APSP) Problem löst, kann das gesuchte Ergebnis berechnen, jedoch mit deutlich<br />

höherem Zeitaufwand. Floyd-Warschall berechnet paarweise die Abstände<br />

von jedem Knoten zu jedem an<strong>der</strong>en Knoten im gesamten Graph. Die allgeme<strong>in</strong>e<br />

Komplexität 4 des Dijkstra-Algorithmus ist O(n 2 + m).<br />

Problemklasse C: Routenän<strong>der</strong>ungen<br />

Diese Klasse be<strong>in</strong>haltet beispielsweise die Routenän<strong>der</strong>ungen <strong>durch</strong> den Ausfall<br />

e<strong>in</strong>zelner Knoten o<strong>der</strong> Kanten. Ebenso s<strong>in</strong>d Routenän<strong>der</strong>ungen <strong>durch</strong> e<strong>in</strong>e<br />

Verschlechterung von Kanten denkbar. Daraus könnte man e<strong>in</strong>e Kenngröße<br />

für die Wichtigkeit e<strong>in</strong>zelner Knoten im Netzwerk ermitteln. Dies ist für die<br />

Netzplanung nutzbar, um e<strong>in</strong>zelne Knoten o<strong>der</strong> Verb<strong>in</strong>dungen zu verstärken.<br />

Um dieses Problem zu lösen, wird e<strong>in</strong> Knoten aus dem Graphen gestrichen,<br />

und dann wie <strong>in</strong> Problemklasse B die Routen neu berechnet. An <strong>der</strong> Ergebnismenge,<br />

kann dann abgefragt werden, wie viele Routen sich <strong>durch</strong> den Wegfall<br />

dieses Knotens verschlechtert haben bzw. gar nicht mehr erreichbar s<strong>in</strong>d. Somit<br />

lässt sich auch e<strong>in</strong> Maß für die Wichtigkeit dieses Knotens def<strong>in</strong>ieren. Diese<br />

Verfahrensweise muss für jeden Knoten e<strong>in</strong>mal ausgeführt werden, um die<br />

Wichtigkeit aller Knoten zu ermitteln. Die Komplexität für dieses Verfahren<br />

beträgt also O(n · (n 2 + m)).<br />

Diese Problemklasse lässt sich sogar noch erweitern. Beispielsweise soll ermittelt<br />

werden, wie stark sich <strong>der</strong> Ausfall von zwei Knoten gleichzeitig auf die<br />

Routen im Netzwerk auswirkt. Dafür müsste man jeweils paarweise zwei Kno-<br />

4 Die amortisiert betrachtete Komplexität kann, für die hier vorliegenden spärlichen Graphen,<br />

noch weiter reduziert werden. Weiteres dazu wird im Abschnitt 4.1.1 erläutert.<br />

© Andreas Redmer — 29. September 2011 8


1. E<strong>in</strong>leitung<br />

ten aus dem Graphen streichen und das Ganze für alle möglichen Knotenpaare<br />

im Graphen <strong>durch</strong>führen. Die Komplexität beträgt dann O(n 2 · (n 2 + m)).<br />

Problemklasse D: Flaschenhalsanalyse<br />

Diese Problemklasse be<strong>in</strong>haltet die Suche nach den Flaschenhälsen im <strong>Netzwerkgraphen</strong>.<br />

Durch die Identifikation e<strong>in</strong>iger Verb<strong>in</strong>dungen als Flaschenhälse<br />

könnten diese gezielt verstärkt werden, um die Netzwerkqualität zu verbessern.<br />

E<strong>in</strong>en Flaschenhals (auch M<strong>in</strong>imaler Schnitt) f<strong>in</strong>det man <strong>in</strong> <strong>der</strong> Graphentheorie<br />

<strong>durch</strong> Lösung des M<strong>in</strong>-Cut-Max-Flow (MCMF) Problems 5 .<br />

E<strong>in</strong> Algorithmus zum F<strong>in</strong>den des Flaschenhalses ist <strong>der</strong> Algorithmus von<br />

Ford und Fulkerson [15]. Verbesserungen dieses Algorithmus s<strong>in</strong>d <strong>der</strong> Algorithmus<br />

von Edmonds und Karp [11] (Komplexität: O(n · m 2 ) ) und <strong>der</strong> Algorithmus<br />

von D<strong>in</strong>ic [9] (Komplexität: O(n 2 · m) ). Im Falle <strong>der</strong> <strong>in</strong> dieser<br />

Arbeit betrachteten <strong>Netzwerkgraphen</strong> s<strong>in</strong>d <strong>der</strong>artig komplexe Algorithmen jedoch<br />

nicht nötig. E<strong>in</strong> Netzwerkgraph kann nicht als Flussgraph (wie etwa bei<br />

Straßensystemen o<strong>der</strong> wasserführenden Rohrleitungen) betrachtet werden, da<br />

für die Pakete jeweils nur e<strong>in</strong>e Route gewählt wird. Wenn e<strong>in</strong> Client sehr viele<br />

Pakete auf e<strong>in</strong>mal sendet, werden diese nicht auf mehrere Routen aufgeteilt.<br />

Das reduziert das MCMF Problem (für diese Graphen) wie<strong>der</strong> auf das F<strong>in</strong>den<br />

<strong>der</strong> kürzesten Wege. Der Flaschenhals ist dann jeweils die ”<br />

schlechteste“<br />

Kante auf dem gefundenen Weg. Da man sich im Zuge <strong>der</strong> Ausführung des<br />

Dijkstra-Algorithmus die ”<br />

schlechteste“ Kante trivial speichern kann, bleibt die<br />

Zeitkomplexität für dieses Verfahren die gleiche. Sie ist auch hier O(n 2 + m).<br />

Problemklasse E: Alternativrouten<br />

Diese Problemklasse be<strong>in</strong>haltet das F<strong>in</strong>den von Alternativrouten. Dabei ist sowohl<br />

die Anzahl als auch die Qualität <strong>der</strong> Alternativrouten <strong>in</strong>teressant. Nutzbar<br />

ist dies für e<strong>in</strong>e Risikoanalyse <strong>der</strong> e<strong>in</strong>zelnen Knoten. Feststellbar wäre<br />

z. B. wie stark sich die Verb<strong>in</strong>dung zum Gateway verschlechtert o<strong>der</strong> ob sie<br />

komplett ausfällt, wenn die beste Route nicht mehr möglich ist. Mit <strong>der</strong> Anzahl<br />

und Qualität von Alternativrouten lassen sich somit Kennzahlen für Netzqualität<br />

und das Ausfallrisiko e<strong>in</strong>zelner Knoten erstellen.<br />

In <strong>der</strong> Graphentheorie werden dazu die k besten Pfade ermittelt. Das zugehörige<br />

Problem ist das k-Shortest-Path (kSP) Problem. Zur Lösung dieses<br />

5 Das M<strong>in</strong>-Cut-Max-Flow Theorem besagt, dass <strong>der</strong> maximale Fluss <strong>in</strong> e<strong>in</strong>em Graphen<br />

immer <strong>durch</strong> se<strong>in</strong>e Engstelle (Flaschenhals) bestimmt wird. M<strong>in</strong>imaler Schnitt (M<strong>in</strong>-Cut)<br />

und maximaler Fluss (Max-Flow) s<strong>in</strong>d vom Betrag her gleich.<br />

© Andreas Redmer — 29. September 2011 9


1. E<strong>in</strong>leitung<br />

Problems, das Yen <strong>in</strong> [33] beschreibt, schlägt er e<strong>in</strong>en auf Bellman-Ford basierenden<br />

Algorithmus vor, da dies sehr e<strong>in</strong>fach zum implementieren ist. Als<br />

Basisalgorithmus, zur Ermittlung <strong>der</strong> kürzesten Pfade, kann jedoch auch <strong>der</strong><br />

Dijkstra verwendet werden. Dieser gibt die k kürzesten Pfade sortiert nach ihrer<br />

Qualität zurück. Wenn man den Dijkstra mit <strong>der</strong> Komplexität O(n 2 + m)<br />

als Basis verwendet, löst <strong>der</strong> Algorithmus von Yen [33] das kSP-Problem mit<br />

<strong>der</strong> Zeitkomplexität von O(k · n · n 2 + m) = O(k · n 3 + m). Dies gilt jedoch nur<br />

unter <strong>der</strong> sehr pessimistischen Annahme, dass <strong>der</strong> gefundene beste Weg alle<br />

Knoten des Graphen enthält. E<strong>in</strong>e erste Untersuchung <strong>der</strong> Ergebnisse <strong>der</strong> Problemklasse<br />

B hat jedoch gezeigt, dass sich auf den kürzesten Wegen maximal<br />

1.78 √ n Knoten bef<strong>in</strong>den (vgl. Anhang A.4). Da<strong>durch</strong> ist e<strong>in</strong>e gute Schätzung für<br />

die obere Schranke <strong>der</strong> Komplexität dieses Problems O(k · 1.78√ n · n 2 + m) =<br />

O(k · n 2.56 + m).<br />

Zusammenfassung<br />

Problemklasse Problem Komplexität Algorithmus<br />

A Knotenaktivität - - -<br />

B Routen SSSP O(n 2 + m) Dijkstra<br />

C Routenän<strong>der</strong>ungen SSSP O(n · (n 2 + m)) Dijkstra<br />

D Flaschenhalsanalyse MCMF O(n 2 + m) Dijkstra<br />

E Alternativrouten kSP O(k · n 2.56 + m) Dijkstra<br />

Tabelle 1.5.: Zusammenfassung <strong>der</strong> Problemklassen<br />

Die Tabelle 1.5 zeigt, dass alle für die Ergebnisse dieser Arbeit wichtigen<br />

Probleme auf den Algorithmus von Dijkstra zurückführbar s<strong>in</strong>d. Somit ist es<br />

das wichtige Ziel, e<strong>in</strong>e sehr performante Dijkstra-Implementierung zu f<strong>in</strong>den.<br />

Diese muss dann <strong>in</strong> e<strong>in</strong>er UDF-Schnittstelle zur Verfügung gestellt werden.<br />

1.5. Vorausgesetzte Hard- und Software<br />

Um die Ergebnisse dieser Arbeit nachvollziehbar zu machen, sollen hier die<br />

Computer beschrieben werden, <strong>der</strong> für die Erstellung <strong>der</strong> Implementierung<br />

verwendet wurden. Tabelle 1.6 zählt alle relevanten Features auf.<br />

Alle <strong>in</strong> dieser Arbeit angegebenen Laufzeiten beziehen sich, wenn nicht an<strong>der</strong>s<br />

angegeben, auf das Testsystem 1. Laufzeiten auf an<strong>der</strong>en Systemen können<br />

entsprechend abweichen. Generell lassen sich alle Ergebnisse auch auf<br />

an<strong>der</strong>en Computern nachvollziehen, es sei denn grundlegende Hardware (wie<br />

© Andreas Redmer — 29. September 2011 10


1. E<strong>in</strong>leitung<br />

z. B. Grafikkarte für GPU Berechnung) o<strong>der</strong> Software (z. B. das DBMS) ist<br />

nicht vorhanden.<br />

Feature Testsystem 1 Testsystem 2<br />

Desktop PC<br />

XEN Virtual Mach<strong>in</strong>e<br />

CPU<br />

AMD Athlon 64 X2 Dual Intel Xeon X5482<br />

Core 4200+<br />

(4 XEN Cores)<br />

CPU Takt 2200 MHz 3200 MHz<br />

CPU Cache<br />

L1/L2<br />

128 KB / 512 KB 64 KB / 6 MB<br />

Arbeitsspeicher 4 GB DDR II - 800 MHz 1 GB DDR II<br />

Festplatte<br />

Samsung HD103SJ 7200<br />

XEN XVDA<br />

RPM, 32MB Cache, 150<br />

Virtual Block Device<br />

MB/s max<br />

Grafikkarte<br />

GeForce GTX 460, 16x<br />

PCIe, 768MB GDDR5<br />

-<br />

RAM<br />

GPU<br />

1350 MHz und 336 CUDA<br />

Cores à 675 MHz<br />

-<br />

Betriebssystem<br />

Ubuntu 10.04.3 LTS, CentOS Release 5.5,<br />

Kernel 2.6.32-32 Kernel 2.6.18-194.32.1<br />

Dateisystem ext4 ext3<br />

DB Server PostgreSQL 8.4.8 x86 64 PostgreSQL 8.4.5 x86 64<br />

Java OpenJDK 1.9.9 OpenJDK 1.7.5<br />

Tabelle 1.6.: Spezifikation <strong>der</strong> Testsysteme<br />

© Andreas Redmer — 29. September 2011 11


2. Stand <strong>der</strong> Technik<br />

2. Stand <strong>der</strong> Technik<br />

2.1. Rout<strong>in</strong>g-Algorithmen<br />

Als Rout<strong>in</strong>g wird <strong>in</strong> Rechnernetzen das Festlegen von Pfaden für die Nachrichtenübermittlung<br />

bezeichnet. Es existiert e<strong>in</strong>e Vielzahl von Algorithmen für<br />

das Rout<strong>in</strong>g. Dabei gibt es zwei generelle Vorgehensweisen:<br />

ˆ L<strong>in</strong>k-State-Verfahren und<br />

ˆ Distanzvektor-Verfahren.<br />

Beim L<strong>in</strong>k-State-Verfahren teilt je<strong>der</strong> Teilnehmer <strong>der</strong> Welt mit, wer se<strong>in</strong>e Nachbarn<br />

s<strong>in</strong>d. Da<strong>durch</strong> ist nach e<strong>in</strong>iger Zeit die gesamte Topologie des Netzwerks<br />

an jedem Knoten verfügbar. Je<strong>der</strong> Knoten kann also alle Pfade selbst berechnen.<br />

Beim Distanzvektor-Verfahren wird auf jedem Knoten nur gespeichert, wie<br />

gut bestimmte Ziele erreichbar s<strong>in</strong>d. Diese Information wird jedem Nachbarn<br />

mitgeteilt. Im Unterschied zu L<strong>in</strong>k-State-Verfahren ist hier auf jedem Knoten<br />

nur e<strong>in</strong> Teil <strong>der</strong> Welt abgespeichert und auch die Berechnung <strong>der</strong> Pfade erfolgt<br />

über mehrere Knoten verteilt. Dafür wird <strong>in</strong> <strong>der</strong> Praxis meist <strong>der</strong> Algorithmus<br />

von Dijkstra [8] o<strong>der</strong> <strong>der</strong> von Floyd und Warshall [13] e<strong>in</strong>gesetzt.<br />

E<strong>in</strong> Verfahren kann grundsätzlich zentral o<strong>der</strong> dezentral se<strong>in</strong>. Dabei unterscheidet<br />

sich die Lokalität auf <strong>der</strong> <strong>der</strong> Algorithmus ausgeführt wird. Bei<br />

dezentralen Verfahren wird <strong>der</strong> Algorithmus auf allen Knoten ausgeführt, während<br />

dies bei zentralen Verfahren <strong>in</strong> e<strong>in</strong>em Kontrollzentrum geschieht. Auch<br />

die Dynamik e<strong>in</strong>es Verfahrens kann bewertet werden. E<strong>in</strong> sehr dynamisches<br />

Verfahren trifft die Rout<strong>in</strong>gentscheidungen aufgrund des aktuellen Zustandes<br />

des Netzwerks. Bei weniger dynamischen Verfahren bleibt die Rout<strong>in</strong>gtabelle<br />

über längere Zeit unverän<strong>der</strong>t. Zentrale und undynamische Verfahren belasten<br />

das Netzwerk weniger mit Topology Control (TC) Nachrichten. Dafür benutzen<br />

sie jedoch möglicherweise veraltete o<strong>der</strong> unvollständige Informationen über<br />

das Netzwerk.<br />

Im Opennet-Netzwerk wird wird das L<strong>in</strong>k-State-Protokoll OLSR (Optimized<br />

L<strong>in</strong>k State Rout<strong>in</strong>g [6]) als Rout<strong>in</strong>g-Protokoll e<strong>in</strong>gesetzt. Dabei handelt<br />

es sich um e<strong>in</strong>en RFC-Standard, <strong>der</strong> e<strong>in</strong> L<strong>in</strong>k-State-Verfahren für Wireless<br />

Netzwerke beschreibt. Das Verfahren ist dezentral und sehr dynamisch. Die<br />

Rechenlast und <strong>der</strong> Speicheraufwand für die aktuell geltenden Routen müs-<br />

© Andreas Redmer — 29. September 2011 12


2. Stand <strong>der</strong> Technik<br />

sen auf jedem Knoten aufgewandt werden. Etwa 10% <strong>der</strong> Bandbreite wird<br />

im Opennet für TC-Nachrichten aufgewendet. Zur Berechnung <strong>der</strong> kürzesten<br />

Pfade wird <strong>der</strong> Dijkstra-Algorithmus verwendet.<br />

Für den Rout<strong>in</strong>g-Algorithmus s<strong>in</strong>d verschiedene Weisen <strong>der</strong> zukünftigen Verbesserung<br />

denkbar. E<strong>in</strong>e algorithmische Verbesserung könne beispielsweise dafür<br />

sorgen, dass weniger (o<strong>der</strong> kle<strong>in</strong>ere) TC-Nachrichten verschickt werden<br />

müssen (Verbesserung auf OSI Schicht 4). E<strong>in</strong> weiteres Beispiel ist e<strong>in</strong>e Publikation<br />

von Badis und Al Agha [2], <strong>in</strong> <strong>der</strong> sie den Daten<strong>durch</strong>satz <strong>durch</strong><br />

e<strong>in</strong>e Heuristik für die Selektion von MPRs 6 erhöhen. An<strong>der</strong>erseits ist auch die<br />

Verbesserung <strong>der</strong> Hardware (Erhöhung <strong>der</strong> Sendeleistung, ger<strong>in</strong>gfügige physische<br />

Repositionierung <strong>der</strong> WLAN-Antenne, etc.) möglich (Verbesserung auf<br />

OSI-Schicht 1).<br />

In all diesen Fällen wird jedoch e<strong>in</strong>e vorherige <strong>Datenanalyse</strong> benötigt, um<br />

festzustellen welche TC-Nachrichten redundant s<strong>in</strong>d und nicht wie<strong>der</strong>holt versendet<br />

werden müssen o<strong>der</strong> um wichtige Knoten und Verb<strong>in</strong>dungen zu f<strong>in</strong>den,<br />

die dann gezielt verstärkt werden können. Im Rahmen dieser Arbeit wurde<br />

e<strong>in</strong> Framework erstellt mit dem diese <strong>Datenanalyse</strong> sehr performant und sehr<br />

e<strong>in</strong>fach <strong>durch</strong>geführt werden kann.<br />

2.2. Metriken<br />

Unabhängig vom verwendeten Rout<strong>in</strong>g-Algorithmus und dem dar<strong>in</strong> enthaltenen<br />

Graphen-Algorithmus wird e<strong>in</strong>e Metrik benötigt. Auch die Algorithmen<br />

Dijkstra und Floyd-Warshall setzen e<strong>in</strong>e Metrik voraus mit <strong>der</strong> <strong>der</strong> Abstand<br />

von e<strong>in</strong>em Knoten zum an<strong>der</strong>en bestimmt werden kann. Aus mathematischer<br />

Sicht heißt e<strong>in</strong>e Funktion f : M×M → R Metrik, wenn für beliebige a, b, c ∈ M<br />

gilt:<br />

1. f(a, b) ≥ 0<br />

2. f(a, b) = 0 ⇔ a = b<br />

3. f(a, b) = f(b, a)<br />

4. f(a, b) ≤ f(a, c) + f(c, b).<br />

Wenn man auf das dritte Axiom (Symmetrie) verzichtet, erhält man e<strong>in</strong>e Quasimetrik.<br />

E<strong>in</strong>e Metrik <strong>in</strong> e<strong>in</strong>em Rechnernetz ist e<strong>in</strong>e mathematische Quasimetrik, die<br />

e<strong>in</strong> Maß für die Güte e<strong>in</strong>er Verb<strong>in</strong>dung def<strong>in</strong>iert. Die Qualitätswerte s<strong>in</strong>d üblicherweise<br />

<strong>in</strong> e<strong>in</strong>em Intervall def<strong>in</strong>iert und können <strong>in</strong>vers zu e<strong>in</strong>er tatsächlichen<br />

6 MPR: Multipo<strong>in</strong>t Relay, e<strong>in</strong> Knoten <strong>der</strong> Nachrichten an mehrere Empfänger weiterleitet<br />

© Andreas Redmer — 29. September 2011 13


2. Stand <strong>der</strong> Technik<br />

mathematischen Metrik se<strong>in</strong>. Dabei können Verb<strong>in</strong>dungsqualität, Bandbreite,<br />

Verzögerung (Latenzzeiten), aktuelle Last, MTU, Verlässlichkeit, Hop Count<br />

und/o<strong>der</strong> die tatsächlichen Kosten für die physikalische Aufrechterhaltung <strong>der</strong><br />

Verb<strong>in</strong>dung <strong>in</strong> die Berechnung e<strong>in</strong>gehen. Netzwerk-Metriken geben die Güte<br />

des gesamten Pfades zwischen zwei Knoten an. Dies muss ke<strong>in</strong>e direkte Verb<strong>in</strong>dung<br />

se<strong>in</strong>. Sei f(a, b) e<strong>in</strong>e Metrik für e<strong>in</strong>e Verb<strong>in</strong>dung zwischen den Netzwerkknoten<br />

a und b und sei (n 1 = a, n 2 , . . . , n m−1 , n m = b) <strong>der</strong> beschreibende Pfad<br />

zwischen a und b, dann gibt es verschiedene Formen <strong>der</strong> Zusammenführung<br />

<strong>der</strong> e<strong>in</strong>zelnen Teilpfade. Diese können additiv, multiplikativ o<strong>der</strong> konkav se<strong>in</strong><br />

und s<strong>in</strong>d wie folgt def<strong>in</strong>iert:<br />

additiv: f(a, b) = f(n 1 , n m ) =<br />

m−1<br />

∑<br />

multiplikativ: f(a, b) = f(n 1 , n m ) =<br />

i=1<br />

f(n i , n i+1 )<br />

m−1<br />

∏<br />

i=1<br />

f(n i , n i+1 )<br />

konkav: f(a, b) = f(n 1 , n m ) = m−1<br />

m<strong>in</strong><br />

i=1 (f(n i, n i+1 ))<br />

Beispielsweise verwendet man die Übertragungsverzögerung als additive Metrik.<br />

Die Summe <strong>der</strong> Verzögerung auf jedem Teilpfad ergibt den Gesamtwert,<br />

<strong>der</strong> die Qualität <strong>der</strong> Verb<strong>in</strong>dung beschreibt. E<strong>in</strong> Beispiel für e<strong>in</strong>e multiplikative<br />

Metrik ist die Verlustwahrsche<strong>in</strong>lichkeit. Dabei ist das Produkt aller Wahrsche<strong>in</strong>lichkeiten,<br />

auf dem Weg von a zu b, die Gesamtwahrsche<strong>in</strong>lichkeit für den<br />

Paketverlust auf <strong>der</strong> Verb<strong>in</strong>dung. Für e<strong>in</strong>e konkave Metrik ist die Bandbreite<br />

beispielhaft zu nennen. Die ger<strong>in</strong>gste Bandbreite, die auf e<strong>in</strong>em Teilpfad verfügbar<br />

ist, ist <strong>der</strong> Wert für die gesamte Bandbreite auf dem Pfad. Im Opennet<br />

wird die multiplikative Metrik ETX (Expected Transmission Count) verwendet,<br />

die <strong>in</strong> [7] vorgestellt wurde. Diese beschreibt die Wahrsche<strong>in</strong>lichkeit dafür,<br />

dass e<strong>in</strong> Paket von a nach b tatsächlich ankommt.<br />

2.3. Betrachtung <strong>der</strong> bestehenden<br />

Implementierung als Cloud-Service<br />

In diesem Abschnitt wird die Implementierung von Mundt und Vetterick [22]<br />

genauer betrachtet. Sie stellt den aktuellen Stand <strong>der</strong> Technik bezüglich <strong>der</strong><br />

<strong>Datenanalyse</strong> und ihrer <strong>Performanceoptimierung</strong> dar.<br />

© Andreas Redmer — 29. September 2011 14


2. Stand <strong>der</strong> Technik<br />

2.3.1. Algorithmische Komplexität<br />

In dieser Implementierung dauerte die Berechnung <strong>der</strong> kürzesten Wege von<br />

jedem Knoten zu e<strong>in</strong>em Gateway mit dem Dijkstra-Algorithmus e<strong>in</strong>e Sekunde.<br />

Dabei werden <strong>durch</strong>schnittlich 200 Knoten und 800 Kanten verwendet. Da<br />

es <strong>der</strong>zeit vier Gateways gibt, muss <strong>der</strong> Dijkstra-Algorithmus vier mal ausgeführt<br />

werden. Da<strong>durch</strong> ergibt sich e<strong>in</strong>e Ausführungszeit von vier Sekunden, pro<br />

Timestamp, die für die Datenauswertung m<strong>in</strong>destens nötig ist. Da dieser Zeitaufwand<br />

(mehr als zwanzig Tage für e<strong>in</strong> Jahr aufgezeichnete Daten) sehr hoch<br />

ist um effizient damit arbeiten zu können, wurde die Berechnung <strong>in</strong> [22] erfolgreich<br />

parallelisiert. Dafür wurde <strong>der</strong> Cloud-Service von Google verwendet. Je<br />

nach Anzahl <strong>der</strong> verwendeten CPU-Kerne ließ sich die Verarbeitungszeit für<br />

e<strong>in</strong> Jahr aufgezeichnete Daten damit bis auf 20 Stunden reduzieren.<br />

Da das Problem <strong>der</strong> kürzesten Pfade und <strong>der</strong> Dijkstra-Algorithmus <strong>in</strong> vielen<br />

Wissenschaftszweigen sehr verbreitet ist, wird fortlaufend an Optimierungen<br />

für den Algorithmus von Dijkstra geforscht. Derzeitige Implementierungen erreichen<br />

das Ergebnis für e<strong>in</strong>e Millionen Knoten <strong>in</strong> weniger als zehn Sekunden<br />

[17]. Es stellte sich also die Frage, warum die bestehende Implementierung damit<br />

verglichen so langsam ist. Da <strong>in</strong> <strong>der</strong> Implementierung ke<strong>in</strong>e offensichtlichen<br />

Fehler beim Hardwarezugriff o<strong>der</strong> Datenbankzugriff erkennbar s<strong>in</strong>d, liegt die<br />

Vermutung nahe, dass die Komplexität des implementierten Algorithmus zu<br />

hoch se<strong>in</strong> könnte. Deshalb wurde an dieser Stelle e<strong>in</strong>e Komplexitätsbetrachtung<br />

<strong>durch</strong>geführt.<br />

Die Laufzeitkomplexität wurde <strong>durch</strong> Substitution aller Quelltextzeilen <strong>durch</strong><br />

die Ausgabe e<strong>in</strong>er Zeichenkette realisiert. Die Zeichenkette stellt jeweils die<br />

Komplexität für den e<strong>in</strong>zelnen Befehl dar. So wurde je<strong>der</strong> Zugriff auf e<strong>in</strong>e Java<br />

TreeMap (conta<strong>in</strong>sKey, get, put) und je<strong>der</strong> Zugriff auf e<strong>in</strong> Java TreeSet (add,<br />

remove, conta<strong>in</strong>s) <strong>durch</strong> die Zeichenkette ”<br />

log(n)“ ersetzt. Diese Operationen<br />

werden garantiert <strong>in</strong> logarithmischer Zeit ausgeführt 7 . Die Zeichenkette ”<br />

n“<br />

steht für die Anzahl <strong>der</strong> Knoten und ”<br />

m“ für die Anzahl <strong>der</strong> Kanten. Analog<br />

wurde jede Schleife die über alle Knoten iteriert mit ”<br />

n ∗“ ersetzt. Dabei wurde<br />

jeweils <strong>der</strong> Worst-Case (also <strong>der</strong> schlechteste Fall) angenommen.<br />

Ausgabe des Programms war:<br />

n + log(n) + log(n) + log(n) + n ∗ (+n ∗ (+log(m) + n ∗ (+log(n) + m +<br />

log(m))) + m + log(n) + m ∗ (+log(n) + log(n) + log(n)) + log(n) + log(n))<br />

Dieser Term wird vere<strong>in</strong>facht und die konstanten Faktoren werden <strong>der</strong> Lesbarkeit<br />

wegen gestrichen (diese än<strong>der</strong>n die Komplexitätsklasse nicht).<br />

7 Diese Angabe stammt aus <strong>der</strong> Java API Dokumentation [25] <strong>der</strong> jeweiligen Funktionen.<br />

© Andreas Redmer — 29. September 2011 15


2. Stand <strong>der</strong> Technik<br />

n + log(n) + n(n(n(log(n) + log(m) + m) + log(m)) + m · log(n) + log(n) + m)<br />

o<strong>der</strong>:<br />

(((n 3 · log(n) + n 3 · log(m) + n 3 · m) + n 2 · log(m)) + n · m · log(n) + n ·<br />

log(n) + n · m) + log(n) + n<br />

Wie die roten Stellen offenbaren, liegt die Komplexitätsklasse über O(n 3 ), da<br />

<strong>der</strong> Quelltext drei <strong>in</strong>e<strong>in</strong>an<strong>der</strong> verschachtelte Schleifen hat, die bis zu n mal<br />

ausgeführt werden. Der Dijkstra-Algorithmus läuft jedoch eigentlich mit e<strong>in</strong>er<br />

Laufzeit von O(n 2 + m). Wie diese hohe Komplexität zustande kommt, kann<br />

bei <strong>der</strong> Betrachtung des Quelltextes ermittelt werden. Dazu ist <strong>in</strong> List<strong>in</strong>g 2.1<br />

nochmal e<strong>in</strong>e Pseudocode-Schreibweise des o.g. Terms angegeben.<br />

1 n<br />

2 log(n)<br />

3 n mal (<br />

4 n mal (<br />

5 n mal (<br />

6 log(n)<br />

7 log(m)<br />

8 m<br />

9 )<br />

10 log(m)<br />

11 )<br />

12 m mal log(n)<br />

13 log(n)<br />

14 m<br />

15 )<br />

List<strong>in</strong>g 2.1: Pseudocode des Algorithmus aus [22]<br />

Konstante Ausdrücke wurden <strong>in</strong> dieser Darstellung weggelassen. Die Ausdrücke<br />

”<br />

n“ und ”<br />

m“ repräsentieren e<strong>in</strong>e Schleife, <strong>der</strong>en Inhalt jedoch konstant<br />

ist (z. B. ”<br />

n mal 1“ o<strong>der</strong> ”<br />

n mal 3“).<br />

Die ersten beiden Zeilen erhöhen die Laufzeit nicht wesentlich. Dabei handelt<br />

es sich um die übliche Aufbereitung <strong>der</strong> Daten für den Algorithmus. In<br />

vielen Fällen ist auch noch e<strong>in</strong>e Nachbereitung notwendig. Wenn beispielsweise<br />

nach <strong>der</strong> Ausführung des Dijkstra zusätzlich e<strong>in</strong>mal über alle Knoten iteriert<br />

werden muss, so erzeugt dies nur e<strong>in</strong> weiteres ”<br />

+n“ im Term <strong>der</strong> Komplexität.<br />

Die Laufzeit erhöht sich also l<strong>in</strong>ear und somit nur unwesentlich. Die höchsten<br />

Laufzeiten <strong>in</strong> diesem Quelltext s<strong>in</strong>d die Polynomiellen. Also die drei Schleifen,<br />

© Andreas Redmer — 29. September 2011 16


2. Stand <strong>der</strong> Technik<br />

die <strong>in</strong> den Zeilen 3, 4 und 5 beg<strong>in</strong>nen. In diesen Zeilen hat n die folgenden<br />

Bedeutungen:<br />

ˆ n <strong>in</strong> Zeile 3: n u die Anzahl unbearbeiteter Knoten<br />

ˆ n <strong>in</strong> Zeile 4: n b die Anzahl bearbeiteter Knoten<br />

ˆ n <strong>in</strong> Zeile 5: n n die Anzahl <strong>der</strong> Nachbarn e<strong>in</strong>es Knoten<br />

Für den Worst-Case gilt:<br />

aber auch:<br />

n u = n<br />

n b = n.<br />

In jedem Teil des Programmablaufes gilt jedoch:<br />

n = n u + n b<br />

denn es wird <strong>in</strong> jedem Schleifen<strong>durch</strong>lauf e<strong>in</strong> weiterer unbearbeiteter Knoten<br />

als bearbeitet markiert. Tatsächlich wird <strong>der</strong> Inhalt <strong>der</strong> ersten beiden Schleifen<br />

also niemals n 2 mal ausgeführt. Für die Worst-Case-Betrachtung ist dieser<br />

Wert allerd<strong>in</strong>gs richtig, da er für große n gegen n 2 konvergiert.<br />

Die Worst-Case-Annahme, dass sich alle Knoten <strong>in</strong> <strong>der</strong> Nachbarschaft e<strong>in</strong>es<br />

Knotens bef<strong>in</strong>den, ist relativ pessimistisch. Für kle<strong>in</strong>e Netzwerke <strong>in</strong> <strong>der</strong> sich<br />

alle Knoten untere<strong>in</strong>an<strong>der</strong> gegenseitig <strong>in</strong> direkter Funkreichweite bef<strong>in</strong>den, ist<br />

sie dennoch richtig. E<strong>in</strong>e E<strong>in</strong>schränkung für die Komplexitätsbetrachtung kann<br />

also nicht gemacht werden. Somit gilt<br />

n n = n<br />

und es kann n u = n b = n n = n für die Komplexitätsbetrachtung angenommen<br />

werden.<br />

Im Folgenden soll die Komplexität des eigentlichen Dijkstra-Algorithmus<br />

vergleichend betrachtet werden.<br />

© Andreas Redmer — 29. September 2011 17


2. Stand <strong>der</strong> Technik<br />

1 FUNCTION dijkstra() {<br />

2 <strong>in</strong>it()<br />

3 WHILE Q ≠ ∅ {<br />

4 u := m<strong>in</strong>(Q)<br />

5 Q := Q \ {u}<br />

6 FOR EACH NEIGHBOUR v OF u {<br />

7 IF v ∈ Q THEN update(u, v)<br />

8 }<br />

9 }<br />

10 }<br />

List<strong>in</strong>g 2.2: Dijkstra-Algorithmus<br />

<strong>in</strong> Pseudocode<br />

1 FUNCTION dijkstra() {<br />

2 O(n)<br />

3 n mal (<br />

4 O(1)<br />

5 O(n)<br />

6 n mal (<br />

7 O(1)<br />

8 )<br />

9 )<br />

10 }<br />

List<strong>in</strong>g 2.3: Komplexität des<br />

Dijkstra-Algorithmus<br />

1 FUNCTION <strong>in</strong>it() {<br />

2 FOR EACH v ∈ V (G) {<br />

3 dist[v] := ∞<br />

4 pred[v] := null<br />

5 }<br />

6 dist[s] := 0<br />

7 Q := V (G)<br />

8 }<br />

List<strong>in</strong>g 2.4: <strong>in</strong>it-Funktion des<br />

Dijkstra-Algorithmus<br />

1 FUNCTION update(u,v) {<br />

2 new_way := dist[u] + distance(u, v)<br />

3 IF new_way < dist[v] THEN {<br />

4 dist[v] := new_way<br />

5 pred[v] := u<br />

6 }<br />

7 }<br />

8<br />

List<strong>in</strong>g 2.5: update-Funktion des<br />

Dijkstra-Algorithmus<br />

In List<strong>in</strong>g 2.2 ist <strong>der</strong> Algorithmus von Dijkstra <strong>in</strong> Pseudocode dargestellt.<br />

Daneben (List<strong>in</strong>g 2.3) ist zeilenweise die Komplexität für den Algorithmus angegeben.<br />

Der Algorithmus arbeitet mit <strong>der</strong> Prioritätswarteschlange Q. Diese<br />

enthält anfangs die Menge aller Knoten des Graphen (V ). Der aktuell kürzeste<br />

Weg vom Startknoten (s) wird <strong>in</strong> Q als Priorität gespeichert. Die Funktion<br />

m<strong>in</strong>(Q) gibt das Element mit <strong>der</strong> kle<strong>in</strong>sten Priorität zurück. Im Array dist[]<br />

werden die Abstände von allen Knoten zum Startknoten gespeichert. Das Array<br />

pred[] speichert den Vorgänger zu jedem Knoten, <strong>der</strong> auf dem Pfad des<br />

kürzesten Weges liegt.<br />

Die Funktion <strong>in</strong>it() (vgl. List<strong>in</strong>g 2.4) setzt die Startwerte für all diese<br />

Variablen. Sie hat e<strong>in</strong>e Laufzeit von O(n). Die WHILE-Schleife, die <strong>in</strong> Zeile 3<br />

beg<strong>in</strong>nt, arbeitet alle Knoten <strong>in</strong> Q ab und hat somit e<strong>in</strong>e Laufzeit von O(n).<br />

In Zeile 4 und 5 des Dijkstra-Algorithmus (List<strong>in</strong>g 2.2) wird <strong>der</strong> Knoten u<br />

mit dem kle<strong>in</strong>sten Wert <strong>in</strong> Q gefunden und dann aus Q gelöscht. In <strong>der</strong> Praxis<br />

implementiert man sich dafür e<strong>in</strong>e extract_m<strong>in</strong>imum-Funktion die beide<br />

Schritte <strong>in</strong> O(n) ausführt. Dabei liegt die Annahme zu Grunde, dass Q mit<br />

e<strong>in</strong>em e<strong>in</strong>fachen Array programmiert wurde und somit e<strong>in</strong>e l<strong>in</strong>eare Laufzeit<br />

© Andreas Redmer — 29. September 2011 18


2. Stand <strong>der</strong> Technik<br />

für das Auff<strong>in</strong>den e<strong>in</strong>es Elementes hat. An<strong>der</strong>e Datenstrukturen s<strong>in</strong>d dabei<br />

auch möglich. In Zeile 6 des Dijkstra-Algorithmus startet die Schleife, die über<br />

alle Nachbarn des Knotens iteriert. Auch hier wird angenommen, dass sich alle<br />

Knoten <strong>in</strong> <strong>der</strong> Nachbarschaft des Knotens bef<strong>in</strong>den. Somit wird <strong>der</strong> Inhalt<br />

dieser Schleife n mal ausgeführt.<br />

In Zeile 7 kommt <strong>der</strong> wichtige Unterschied zu <strong>der</strong> Implementierung aus [22]<br />

zum Vorsche<strong>in</strong>. Die Abfrage, ob e<strong>in</strong> Knoten schon bearbeitet wurde, muss nicht<br />

auf das Suchen e<strong>in</strong>es Elementes <strong>in</strong> e<strong>in</strong>er Menge (v ∈ Q) zurückgeführt werden.<br />

Dies wäre genau so aufwändig wie die Verwendung von zwei disjunkten Mengen<br />

für bearbeitete und unbearbeitete Elemente (wie <strong>in</strong> List<strong>in</strong>g 2.1). Stattdessen<br />

kann hier e<strong>in</strong>fach e<strong>in</strong> boolesches Array angelegt werden, das die Information<br />

speichert ob <strong>der</strong> Knoten schon bearbeitet wurde o<strong>der</strong> nicht. Somit kann die<br />

Abfrage v ∈ Q <strong>in</strong> konstanter Zeit ausgeführt werden. Da die Funktion update<br />

(List<strong>in</strong>g 2.5) ebenfalls nur konstante Laufzeit hat, ist die gesamte Zeile 7 mit<br />

konstantem Zeitaufwand ausführbar.<br />

Für den gesamten Algorithmus <strong>in</strong> List<strong>in</strong>g 2.2 ergibt sich somit, wie man <strong>in</strong><br />

List<strong>in</strong>g 2.3 auch erkennt, e<strong>in</strong>e Komplexität von:<br />

O(n + n · (n + n)) = O(n + 2n 2 )<br />

Diese Laufzeit ist deutlich ger<strong>in</strong>ger als die für das im List<strong>in</strong>g 2.1 angegebene<br />

Verfahren.<br />

Abschließend sei noch bemerkt, dass die obere Schranke noch niedriger angesetzt<br />

werden kann. Aufgrund <strong>der</strong> Vorbed<strong>in</strong>gungen wird die Funktion update<br />

tatsächlich nur m mal ausgeführt. Die Anzahl <strong>der</strong> Nachbarn pro Knoten ist<br />

zwar variabel, aber über den gesamten Algorithmus betrachtet s<strong>in</strong>kt die Anzahl<br />

<strong>der</strong> Nachbarn für den Knoten, die noch nicht bearbeitet wurden. Die Funktion<br />

update wird nur e<strong>in</strong>mal pro Kante ausgeführt. Da<strong>durch</strong> erhält man die noch<br />

ger<strong>in</strong>gere Komplexität von<br />

O(n + n 2 + m).<br />

Dabei ist <strong>der</strong> erste Teil (n) die Vorbereitung bzw. Initialisierung <strong>der</strong> Daten und<br />

<strong>der</strong> zweite Teil (n 2 + m) die eigentliche Laufzeit e<strong>in</strong>es Dijkstra-Algorithmus.<br />

2.3.2. Vor- und Nachteile <strong>der</strong> Cloudlösung<br />

Für die Implementierung aus [22] wurde die ”<br />

App Eng<strong>in</strong>e“ benutzt. So bezeichnet<br />

<strong>der</strong> Anbieter Google se<strong>in</strong>e Cloud. Der größte Vorteil war e<strong>in</strong>e klare<br />

© Andreas Redmer — 29. September 2011 19


2. Stand <strong>der</strong> Technik<br />

Beschleunigung des Programmablaufes <strong>durch</strong> die hochgradige Parallelisierung.<br />

Weiterh<strong>in</strong> ist die Nutzung von kostengünstigen On-Demand-Ressourcen e<strong>in</strong><br />

entscheiden<strong>der</strong> Vorteil. Mundt und Vetterick rechnen <strong>in</strong> ihrer Publikation exemplarisch<br />

vor, dass die Nutzung <strong>der</strong> Cloud kostengünstiger ist als die Verwendung<br />

(Anschaffung und Wartung) e<strong>in</strong>es eigenen Servers.<br />

Im Rahmen dieser Arbeit wird e<strong>in</strong> eigener Datenbankserver verwendet. Dieser<br />

ist jedoch konzeptionell gleich mit dem Computer <strong>der</strong> die Datenaufzeichnung<br />

anfertigt, denn Ziel ist es ja die Berechnungen direkt <strong>in</strong> <strong>der</strong> Datenbank<br />

auszuführen. Abhängig davon, wie sehr <strong>der</strong> aufzeichnende Knoten <strong>durch</strong> auf<br />

ihm laufende <strong>Datenanalyse</strong>n ausgelastet werden darf, ist für das hier entwickelte<br />

Konzept ke<strong>in</strong> separater Server notwendig. Bezüglich <strong>der</strong> Geschw<strong>in</strong>digkeit<br />

sollten die Ergebnisse, die mit den User Def<strong>in</strong>ed Functions (UDF) erreicht<br />

werden idealerweise so schnell se<strong>in</strong>, dass ihre Verwendung lohnenswerter ist als<br />

die <strong>der</strong> ”<br />

App Eng<strong>in</strong>e“.<br />

Der größte Nachteil bei <strong>der</strong> Berechnung <strong>in</strong> <strong>der</strong> Cloud ist <strong>der</strong> teure Upload<br />

<strong>der</strong> Daten <strong>in</strong> die Cloud. Dabei s<strong>in</strong>d nicht nur die monetären Kosten son<strong>der</strong>n<br />

auch <strong>der</strong> benötigte Zeitaufwand entscheidend. Weiterh<strong>in</strong> ist die Benutzung<br />

<strong>der</strong> ”<br />

App Eng<strong>in</strong>e“ an das dokumentbasierte Datenbanksystem ”<br />

Bigtable“ [5]<br />

von Google gebunden. Dies br<strong>in</strong>gt die generellen Vorteile e<strong>in</strong>es NoSQL DBMS<br />

mit sich. Beispielsweise ist es möglich sehr große Datenmengen im dreistelligen<br />

Terabyte-Bereich zu speichern und schnell auf diese zugreifen zu können.<br />

Dies br<strong>in</strong>gt jedoch den Nachteil, dass die Anfragesprache verglichen mit SQL<br />

deutlich reduzierter ist. Sämtliche Data M<strong>in</strong><strong>in</strong>g Funktionen aus dem SQL’97<br />

Standard, können bei NoSQL-Datenbanken nicht genutzt werden. Somit muss<br />

die <strong>Datenanalyse</strong> immer <strong>in</strong> e<strong>in</strong>em zusätzlichen Programm stattf<strong>in</strong>den. Weiterh<strong>in</strong><br />

bietet die Google ”<br />

App Eng<strong>in</strong>e“ verständlicherweise e<strong>in</strong>e e<strong>in</strong>geschränkte<br />

Version <strong>der</strong> Java API an. Dies ist zwar nicht zwangsweise e<strong>in</strong> Nachteil, könnte<br />

aber unter Umständen den Entwicklungsaufwand erhöhen.<br />

In dieser Arbeit können die eben genannten Nachteile beseitigt werden. Zunächst<br />

entfällt <strong>der</strong> teure Upload, da e<strong>in</strong> eigener Server benutzt wird. Die E<strong>in</strong>schränkung<br />

<strong>der</strong> Anfragesprache kann aufgehoben werden, <strong>in</strong>dem e<strong>in</strong> relationales<br />

DBMS benutzt wird. Näheres dazu wird im Abschnitt 3.1.1 erläutert. Die<br />

E<strong>in</strong>schränkung <strong>der</strong> Programmiersprache bzw. <strong>der</strong> API kann ebenfalls <strong>durch</strong><br />

die richtige Auswahl verh<strong>in</strong><strong>der</strong>t werden. Dies wird später im Abschnitt 3.1.3<br />

genauer erörtert.<br />

© Andreas Redmer — 29. September 2011 20


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

3. Vorbetrachtungen e<strong>in</strong>er<br />

hochperformanten Lösung<br />

3.1. Wahl des DBMS und <strong>der</strong><br />

Programmiersprache<br />

Die Topologie<strong>in</strong>formationen des Mesh-Netzwerks werden an e<strong>in</strong>em zentralen<br />

Punkt im Netzwerk e<strong>in</strong>mal pro M<strong>in</strong>ute aufgezeichnet. Dabei läuft auf diesem<br />

Knotenpunkt e<strong>in</strong> Java-Programm, dass die Informationen über e<strong>in</strong>e JDBC-<br />

Schnittstelle auf e<strong>in</strong>em MySQL Datenbankserver speichert. Die ursprüngliche<br />

Idee zu dieser Arbeit ist es, User-Def<strong>in</strong>ed-Functions (UDF) direkt auf diesem<br />

Datenbankserver auszuführen und da<strong>durch</strong> die Laufzeitperformance zu verbessern.<br />

Im Folgenden soll zunächst untersucht werden, <strong>in</strong>wiefern <strong>der</strong> Umstieg auf<br />

e<strong>in</strong> an<strong>der</strong>es DBMS s<strong>in</strong>nvoll ist und welches Datenmodell das DBMS verwenden<br />

sollte. Die Verwendung e<strong>in</strong>es an<strong>der</strong>en DBMS wäre denkbar, wenn sich da<strong>durch</strong><br />

entscheidende Unterschiede <strong>in</strong> <strong>der</strong> Performance ergeben und die Umstellung auf<br />

das neue DBMS e<strong>in</strong>fach realisierbar ist.<br />

3.1.1. Wahl des Datenbankmodells<br />

Da MySQL e<strong>in</strong> relationales DBMS ist, werden die Daten bisher relational, also<br />

zeilenweise <strong>in</strong> Tabellen gespeichert. Neben dem relationalen Datenbankmodell<br />

existieren noch folgende Formen:<br />

ˆ Netzwerkartig: Die Datenobjekte werden als Knoten <strong>in</strong> e<strong>in</strong>em Graphen<br />

betrachtet, die mit Kanten untere<strong>in</strong>an<strong>der</strong> verbunden se<strong>in</strong> können.<br />

ˆ Objektorientiert: Die Datenobjekte s<strong>in</strong>d als komplexe Objekte aus e<strong>in</strong>fachen<br />

Datentypen erstellt. Beziehungen zwischen den Objekten können<br />

Komposition, Aggregation o<strong>der</strong> Generalisierung se<strong>in</strong>.<br />

ˆ Dokumentbasiert: Die Datenobjekte werden als Dokumente gespeichert,<br />

<strong>der</strong>en Struktur jedoch nicht pr<strong>in</strong>zipiell festgelegt ist. Im Gegensatz zum<br />

objektorientierten Ansatz hat e<strong>in</strong> Datenobjekt hier e<strong>in</strong>e variable Anzahl<br />

© Andreas Redmer — 29. September 2011 21


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

von Eigenschaften. Auch die Beziehungen zwischen Datenobjekten werden<br />

über die Eigenschaften variabel gestaltet.<br />

ˆ Hierarchisch: Die Datenobjekte stehen <strong>in</strong> Parent-Child-Beziehungen zue<strong>in</strong>an<strong>der</strong>.<br />

Es existieren auch Mischformen.<br />

Netzwerkartige Speicherung<br />

Die Topologiedaten des Mesh-Netzwerks s<strong>in</strong>d naturgemäß als netzwerkartige<br />

Daten zu betrachten, die <strong>der</strong>zeit jedoch <strong>in</strong> relationaler Form gespeichert<br />

werden. Es existiert also e<strong>in</strong> Mapp<strong>in</strong>g, das die netzwerkartige Form <strong>der</strong> Daten<br />

<strong>in</strong> die relationale (tabellarische) Form br<strong>in</strong>gt. In diesem Fall wird e<strong>in</strong>e<br />

erweiterte Form e<strong>in</strong>er Adjazenzliste <strong>in</strong> <strong>der</strong> Tabelle gespeichert. Diese wurde<br />

gewählt, weil sich die Daten <strong>in</strong> ähnlicher Form auch <strong>in</strong> <strong>der</strong> OLSR-Software<br />

des Knotenpunktes bef<strong>in</strong>den und weil sich die zeilenweise Speicherung <strong>in</strong> <strong>der</strong><br />

MySQL-Datenbank sehr e<strong>in</strong>fach implementieren ließ.<br />

Um die <strong>in</strong> <strong>der</strong> Tabelle 1.1 (Seite 3) <strong>in</strong> e<strong>in</strong>em netzwerkartigen DBMS (im folgenden<br />

Graph-DBMS genannt) zu speichern, kann man auf zwei verschiedene<br />

Arten vorgehen:<br />

1. Man speichert alle Knoten des Mesh-Netzwerkes <strong>in</strong> e<strong>in</strong> großes Netzwerk<br />

<strong>in</strong> <strong>der</strong> Datenbank ab. Dabei bildet man aus dem Timestamp und <strong>der</strong><br />

IP-Adresse e<strong>in</strong>en geme<strong>in</strong>samen, e<strong>in</strong>deutigen Schlüssel für e<strong>in</strong>en Knoten.<br />

Alle Knoten aus allen Timestamps werden dann <strong>in</strong> das selbe Netzwerk<br />

e<strong>in</strong>gefügt, wobei nur Verb<strong>in</strong>dungen zwischen Knoten des selben Timestamps<br />

existieren. Hier muss das DBMS also e<strong>in</strong>en sehr großen Graph<br />

speichern können.<br />

2. Man speichert jeden Timestamp (also jedes separate Abbild) <strong>in</strong> e<strong>in</strong> neues<br />

Netzwerk <strong>in</strong> <strong>der</strong> Datenbank. Dabei wäre <strong>der</strong> Timestamp jeweils <strong>der</strong> Name<br />

des Netzwerks und die Knoten wären die IP-Adressen <strong>der</strong> Knotenpunkte<br />

aus dem Mesh-Netzwerk. Die Verb<strong>in</strong>dungen zwischen den Knoten werden<br />

wie im tatsächlichen Mesh-Netzwerk gespeichert. Hier muss das DBMS<br />

also sehr viele e<strong>in</strong>zelne Graphen speichern können.<br />

Welche dieser beiden Speicherstrategien man wählt, hängt von <strong>der</strong> <strong>in</strong>ternen<br />

Speicherung <strong>der</strong> Graphen im DBMS ab. Mo<strong>der</strong>ne DBMS s<strong>in</strong>d darauf optimiert<br />

auch sehr große Netzwerke o<strong>der</strong> Graphen effizient speichern zu können. Die<br />

erste <strong>der</strong> genannten Strategien eignet sich also gut, wobei <strong>der</strong> Timestamp <strong>in</strong><br />

irgende<strong>in</strong>er Weise <strong>in</strong>diziert werden müsste. Schließlich werden die Graphenalgorithmen<br />

(z. B. das F<strong>in</strong>den <strong>der</strong> kürzesten Pfade) jeweils nur ”<br />

pro Timestamp“<br />

© Andreas Redmer — 29. September 2011 22


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

ausgeführt. Somit muss die Gesamtheit aller Knoten e<strong>in</strong>es Timestamps immer<br />

effizient selektierbar se<strong>in</strong>. Gleichzeitig müsste dafür gesorgt werden, dass die<br />

Knoten mit gleichem Timestamp immer möglichst auf <strong>der</strong> selben Seite gespeichert<br />

und nicht im gesamten Speicherbereich verteilt werden.<br />

Im Rahmen dieser Arbeit, konnte ke<strong>in</strong> DBMS mit netzwerkartiger Speicherung<br />

gefunden werden, das Open-Source vertrieben wird o<strong>der</strong> die Art <strong>der</strong><br />

Speicherung offen dokumentiert. E<strong>in</strong> weiter Nachteil ist, dass die populären<br />

Graph-DBMS kostenpflichtig s<strong>in</strong>d und deshalb für diese Arbeit nicht geeignet.<br />

Auch konnten ke<strong>in</strong>e Graph-DBMS gefunden werden, <strong>in</strong> denen User Def<strong>in</strong>ed<br />

Functions <strong>in</strong> <strong>der</strong> Anfragesprache def<strong>in</strong>ierbar s<strong>in</strong>d.<br />

E<strong>in</strong> weiterer <strong>in</strong>teressanter Ansatz für die Speicherung <strong>der</strong> Daten ist, e<strong>in</strong>e<br />

Mapp<strong>in</strong>g-Strategie zu f<strong>in</strong>den, welche die Graphen effizient <strong>in</strong> e<strong>in</strong>em relationalen<br />

DBMS speichert. Dabei kann man e<strong>in</strong> kostenloses DBMS verwenden und<br />

man könnte selbst dafür Sorge tragen, dass die Speicherung für die eigenen<br />

Zwecke effizient geschieht. Dabei könnten Datenzugriffe über UDF gemacht<br />

werden, wie es beispielsweise bei PostGIS[28] gemacht wurde. Aber auch bei<br />

solchen Ansätzen ist festzustellen, dass sie für sehr große Datenmengen entworfen<br />

wurden. Das bedeutet, dass <strong>der</strong> Graph so groß ist, dass er nicht <strong>in</strong> e<strong>in</strong>e<br />

Speicherseite passt (bei PostgreSQL vier Kilobytes). PostGIS wurde beispielsweise<br />

für die Verwaltung geographischer Daten (z. B. Straßennetze) entwickelt.<br />

Die <strong>in</strong> dieser Arbeit behandelten <strong>Netzwerkgraphen</strong> s<strong>in</strong>d so kle<strong>in</strong> (≈ 200 Knoten),<br />

dass sie ke<strong>in</strong>er beson<strong>der</strong>en Speicherform bedürfen um die Geschw<strong>in</strong>digkeit<br />

des Datenzugriffs zu erhöhen.<br />

Objektorientierte Speicherung<br />

Weiterh<strong>in</strong> soll nun die Speicherung <strong>in</strong> e<strong>in</strong>er objektorientierten Datenbank untersucht<br />

werden. Die <strong>in</strong> Tabelle 1.1 (Seite 3) gezeigten Daten können, wie <strong>in</strong><br />

Abbildung 3.1 als UML-Klassendiagramm gezeigt, objektorientiert gespeichert<br />

werden.<br />

L<strong>in</strong>k<br />

+KnotenA:Str<strong>in</strong>g<br />

+KnotenB:Str<strong>in</strong>g<br />

+LQ:float<br />

+NLQ:float<br />

1 1..*<br />

Zeitpunkt<br />

+Datum:date<br />

+Uhrzeit:time<br />

+L<strong>in</strong>ks:L<strong>in</strong>k[]<br />

Abbildung 3.1.: Objektorientierte Speicherung <strong>der</strong> Daten (UML-<br />

Klassendiagramm)<br />

© Andreas Redmer — 29. September 2011 23


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

E<strong>in</strong> L<strong>in</strong>k ist dabei e<strong>in</strong> Vier-Tupel bestehend aus Knoten A, Knoten B, L<strong>in</strong>k<br />

Quality und Neighbour L<strong>in</strong>k Quality. E<strong>in</strong>e Liste dieser L<strong>in</strong>ks stellt dabei e<strong>in</strong>e<br />

Repräsentation des Graphen zu e<strong>in</strong>em Zeitpunkt da. Zusätzlich zu <strong>der</strong> Liste<br />

wird <strong>der</strong> Timestamp dieses Zeitpunktes gespeichert. Neben <strong>der</strong> Speicherung<br />

<strong>in</strong> e<strong>in</strong>em objektorientierten DBMS wäre auch e<strong>in</strong> objektrelationales Mapp<strong>in</strong>g<br />

denkbar. Dabei würden die objektorientierten Inhalte <strong>in</strong> e<strong>in</strong>er relationalen Datenbank<br />

gespeichert werden. Für sehr e<strong>in</strong>fache Objekte, die ke<strong>in</strong>e komplizierten<br />

objektorientierten Methoden verwenden, wäre es auch denkbar die Datensätze<br />

als erweiterte Datentypen (auch TypeDefs o<strong>der</strong> Structs genannt) <strong>in</strong> Tabellen<br />

zu speichern. List<strong>in</strong>g 3.1 zeigt wie man <strong>der</strong>artige Datentypen <strong>in</strong> PostgreSQL<br />

def<strong>in</strong>ieren kann. Durch diese Speicherung wird jedoch we<strong>der</strong> Performance noch<br />

Usability erhöht. Je nach gewählter Speicherform könnte sich <strong>durch</strong> diese Speicherform<br />

<strong>der</strong> verwendete Speicherplatz ger<strong>in</strong>gfügig reduzieren lassen. Dies verursacht<br />

jedoch zusätzlichen Zeitaufwand beim Zugriff auf die Daten.<br />

1 CREATE TYPE L<strong>in</strong>k AS (<br />

2 KnotenA varchar,<br />

3 KnotenB varchar,<br />

4 LQ float,<br />

5 NLQ float );<br />

6<br />

7 CREATE TYPE Zeitpunkt AS (<br />

8 DatumUndUhrzeit timestamp,<br />

9 L<strong>in</strong>ks L<strong>in</strong>k[] );<br />

List<strong>in</strong>g 3.1: Typendef<strong>in</strong>itionen <strong>in</strong> PostreSQL<br />

Dokumentbasierte Speicherung<br />

Die Daten können auch auf verschiedene Weisen <strong>in</strong> e<strong>in</strong>em dokumentbasierten<br />

Datenbanksystem gespeichert werden. In [22] wurde die <strong>Datenanalyse</strong> für<br />

die hier behandelten Mesh-Netzwerke <strong>in</strong> die Google-Cloud ausgelagert, um<br />

sie zu beschleunigen. Dafür ist es nötig, die Daten auf dem Datenbankserver<br />

von Google abzulegen. Dabei wird das Datenbanksystem BigTable verwendet,<br />

welches <strong>in</strong> [5] vorgestellt wurde. Dokumentbasierte Datenbanken werden bevorzugt<br />

benutzt, wenn sehr große Datenmengen gespeichert werden müssen.<br />

Da<strong>durch</strong> gew<strong>in</strong>nen sie <strong>der</strong>zeit, <strong>in</strong> <strong>der</strong> <strong>in</strong>dustriellen und wissenschaftlichen Verwendung,<br />

immer mehr an Bedeutung. Verglichen mit dem relationalen Ansatz<br />

<strong>der</strong> Datenspeicherung, werden die Daten beim dokumentbasierten Ansatz nicht<br />

mehr normalisiert. Es werden also auch redundante Daten gespeichert und ke<strong>in</strong>e<br />

Prüfungen auf Konsistenz mehr <strong>durch</strong>geführt. Die Verwendung von Jo<strong>in</strong>s<br />

© Andreas Redmer — 29. September 2011 24


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

und Constra<strong>in</strong>ts macht das relationale Konzept sehr langsam. Da<strong>durch</strong> haben<br />

relationale Datenbanken bei sehr großen Datenmengen performancebed<strong>in</strong>gt<br />

ke<strong>in</strong>e praktische Bedeutung mehr. Allerd<strong>in</strong>gs s<strong>in</strong>d da<strong>durch</strong> auch die Anfragen<br />

an die Daten komplizierter. Es kann ke<strong>in</strong> SQL o<strong>der</strong> nur noch e<strong>in</strong>e sehr e<strong>in</strong>geschränkte<br />

Form von SQL verwendet werden. Derartige mo<strong>der</strong>ne Datenbanken<br />

werden auch als NoSQL-Datenbanken bezeichnet.<br />

Aufgrund <strong>der</strong> Datenmengen ist e<strong>in</strong> Wechsel von MySQL auf e<strong>in</strong>e dokumentbasierte<br />

Datenbank <strong>der</strong>zeit nicht nötig, da die gespeicherten Daten nur etwa<br />

40 Gigabytes (<strong>in</strong> MySQL) belegen. Die textuelle Repräsentation <strong>der</strong> Daten <strong>in</strong><br />

e<strong>in</strong>er e<strong>in</strong>fachen CSV-Datei ist 15,3 Gigabytes groß. Der Import dieser Daten<br />

<strong>in</strong> e<strong>in</strong>e PostgreSQL-Tabelle mit den tatsächlichen Datentypen (und nicht ihrer<br />

textuelle Form) ist 16 Gigabytes groß. Jonathan Ellis (e<strong>in</strong> Entwickler des dokumentbasierten<br />

Datenbanksystems Cassandra [1]) gibt <strong>in</strong> e<strong>in</strong>em Vortrag [12]<br />

an, dass es für die Performance ke<strong>in</strong>en Unterschied macht, ob man Cassandra<br />

o<strong>der</strong> e<strong>in</strong>e relationale Datenbank e<strong>in</strong>setzt, wenn man ke<strong>in</strong>e verteilte Datenbank<br />

benutzt und ke<strong>in</strong>e Jo<strong>in</strong>s und Constra<strong>in</strong>ts verwendet. Da im Rahmen dieser Arbeit<br />

nur e<strong>in</strong>e Tabelle von Datenaufzeichnungen verwendet wird, die <strong>durch</strong>aus<br />

Fehler <strong>in</strong> <strong>der</strong> Konsistenz und redundante Informationen enthält (vgl. Abschnitt<br />

1.3), ist <strong>der</strong> Umstieg auf e<strong>in</strong> dokumentbasiertes DBMS nicht nötig.<br />

Hierarchische Speicherung<br />

Die Speicherung <strong>in</strong> e<strong>in</strong>em hierarchischen Datenbankmodell ist für diese Daten<br />

nicht s<strong>in</strong>nvoll, da sie ke<strong>in</strong>e s<strong>in</strong>nvolle hierarchische Repräsentation haben.<br />

Zusammenfassung<br />

Abschließend ist zu bemerken, dass im Rahmen dieser Arbeit die Speicherung<br />

<strong>der</strong> Daten <strong>in</strong> relationaler Form beibehalten wurde. Der Hauptgrund dafür ist,<br />

dass das Programm für die Datenaufzeichung nicht geän<strong>der</strong>t werden muss.<br />

Beim Wechsel auf e<strong>in</strong> an<strong>der</strong>es relationales DBMS würde sich auch <strong>der</strong> Import<br />

und Export <strong>der</strong> Daten e<strong>in</strong>facher gestalten, als bei e<strong>in</strong>em Wechsel des Datenbankmodells.<br />

Im folgenden Abschnitt soll deshalb untersucht werden, ob e<strong>in</strong><br />

Wechsel auf e<strong>in</strong> an<strong>der</strong>es relationales DBMS s<strong>in</strong>nvoll ist und welche Vor- und<br />

Nachteile dieser hätte.<br />

© Andreas Redmer — 29. September 2011 25


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

3.1.2. Wahl des DBMS<br />

Als populärste Vertreter von relationalen DBMS s<strong>in</strong>d Oracle, DB2, MS SQL<br />

Server, PostgreSQL und MySQL zu nennen. An das gesuchte System s<strong>in</strong>d<br />

jedoch folgende Bed<strong>in</strong>gungen zu stellen:<br />

1. Das DBMS sollte kostenlos o<strong>der</strong> sehr kostengünstig verwendbar se<strong>in</strong>, da<br />

es später nur für die <strong>Datenanalyse</strong> beim Opennet e.V. verwendet werden<br />

soll.<br />

2. Das DBMS sollte plattformunabhängig se<strong>in</strong>, um e<strong>in</strong>e Installation auf dem<br />

aufzeichnenden Opennet-Knoten zu ermöglichen.<br />

3. Wenn es e<strong>in</strong>e Lizenz, die für Forschungszwecke o. ä. frei verwendbar ist,<br />

darf diese nicht so weit e<strong>in</strong>geschränkt se<strong>in</strong>, dass sie mit den Voraussetzungen<br />

für dieses Projekt kollidiert. So bietet Oracle beispielsweise e<strong>in</strong>e<br />

Version an, die für studentische Forschungen frei ist, diese kann jedoch<br />

nur Daten bis zu zwei Gigabytes verwalten.<br />

Nach Beachtung dieser Kriterien bleibt letztlich nur die Wahl, bei dem bestehenden<br />

MySQL-Server zu bleiben o<strong>der</strong> zu e<strong>in</strong>em PostgreSQL-Server zu wechseln.<br />

Dazu ist zu sagen, dass PostgreSQL das e<strong>in</strong>zige völlig kostenlos verwendbare<br />

<strong>der</strong> genannten Systeme ist. MySQL hat e<strong>in</strong> relativ komplexes Lizenzmodell<br />

und ist unter gewissen Umständen auch kostenpflichtig. Generell ist<br />

PostgreSQL funktionaler und kann größere Datenmengen besser handhaben<br />

als MySQL [20] [30] [31]. Dafür ist die Installation und Bedienung von MySQL<br />

deutlich e<strong>in</strong>facher.<br />

Bei MySQL gibt es lediglich zwei Möglichkeiten User Def<strong>in</strong>ed Functions<br />

(UDF) anzulegen. Die e<strong>in</strong>e ist, das Anlegen von sogenannten Stored Procedures,<br />

wobei es sich um e<strong>in</strong>e Abfolge von SQL-Anweisungen handelt. Diese s<strong>in</strong>d<br />

nicht tur<strong>in</strong>gvollständig und lassen da<strong>durch</strong> unter Umständen die Implementierung<br />

<strong>der</strong> benötigten Graphenalgorithmen nicht zu. Die zweite Möglichkeit<br />

ist die C++ Schnittstelle für UDF bei MySQL zu verwenden. Diese werden<br />

dann kompiliert und als Teil des Datenbankservers geladen. Die Programmierung<br />

solcher C++ UDF ist sehr rudimentär und aufwändig. Dabei können viele<br />

Fehler entstehen, die mit mo<strong>der</strong>neren Programmiersprachen ausgeschlossen<br />

s<strong>in</strong>d. Da MySQL selbst auch <strong>in</strong> C++ entwickelt ist, muss bei <strong>der</strong> Verwendung<br />

von Bibliotheken (wie z. B. stdlib) auf die richtige Version geachtet werden.<br />

Das Debugg<strong>in</strong>g solcher UDF gestaltet sich schwierig und es entsteht e<strong>in</strong> recht<br />

hoher Entwicklungsaufwand.<br />

Gleichzeitig wurde PostgreSQL als DBMS betrachtet, für das es verschiedene<br />

Konzepte zum Erstellen von UDF <strong>in</strong> verschiedenen Programmiersprachen<br />

gibt. Die beiden für MySQL genannten Möglichkeiten existieren <strong>in</strong> PostgreSQL<br />

© Andreas Redmer — 29. September 2011 26


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

ebenfalls. Zusätzlich gibt es die Möglichkeit über e<strong>in</strong>e generelle Schnittstelle<br />

Unterstützung für neue Programmiersprachen h<strong>in</strong>zuzufügen. Dabei ist es<br />

grundsätzlich nicht vorgegeben, ob die Funktion e<strong>in</strong> L<strong>in</strong>k auf e<strong>in</strong>e kompilierte<br />

Funktion <strong>in</strong> e<strong>in</strong>er Bibliothek ist, o<strong>der</strong> ob <strong>der</strong> Inhalt <strong>der</strong> Funktion (als Zeichenkette)<br />

im Quelltext auf dem Server gespeichert ist und bei Bedarf <strong>in</strong>terpretiert<br />

und ausgeführt wird. Es können jeweils e<strong>in</strong>fache Funktionen mit elementarem<br />

Datentyp, Tabellenfunktionen, Aggregatfunktionen und Triggerfunktionen erstellt<br />

werden. H<strong>in</strong>zu kommt die Möglichkeit, dass PostgreSQL eigene komplexe<br />

Datentypen aus e<strong>in</strong>fachen Datentypen (o<strong>der</strong> Arrays davon) def<strong>in</strong>ieren kann.<br />

Diese können wie<strong>der</strong>um als Werte <strong>in</strong> Tabellen, Indizes und UDF (E<strong>in</strong>- und<br />

Ausgabe) verwendet werden. Dazu ist zu bemerken, dass die Speicherung e<strong>in</strong>es<br />

komplexen Datentyps o<strong>der</strong> Arrays <strong>in</strong> e<strong>in</strong>er Tabellenspalte pr<strong>in</strong>zipiell <strong>der</strong><br />

ersten Normalform bei <strong>der</strong> Normalisierung von relationalen Datenbanken wi<strong>der</strong>spricht.<br />

Allerd<strong>in</strong>gs können da<strong>durch</strong> meist Jo<strong>in</strong>s e<strong>in</strong>gespart werden, um die<br />

Performance <strong>der</strong> Datenbankanfragen zu erhöhen. Man muss also <strong>in</strong> <strong>der</strong> Praxis<br />

genau wissen, wann <strong>der</strong> E<strong>in</strong>satz solcher Features s<strong>in</strong>nvoll ist.<br />

Aufgrund <strong>der</strong> größeren Auswahl bei PostgreSQL und <strong>der</strong> generell besseren<br />

Performance gegenüber MySQL-Datenbanken (vgl. [20]) wurde die Entscheidung<br />

getroffen PostgreSQL für diese Arbeit als DBMS zu verwenden. Die Untersuchungen<br />

<strong>in</strong> Abschnitt 3.1.3 bestätigen dies als gute Entscheidung. Der<br />

Export <strong>der</strong> Daten aus <strong>der</strong> MySQL-Datenbank (<strong>in</strong> e<strong>in</strong>e CSV-Datei) und <strong>der</strong><br />

Import <strong>in</strong> die PostgreSQL-Datenbank ist sehr e<strong>in</strong>fach. Auch die permanente<br />

Umstellung des Java-Programms, das die Daten aufzeichnet, ist sehr e<strong>in</strong>fach,<br />

da die verwendeten SQL-Anweisungen (INSERT INTO) bei PostgreSQL die gleiche<br />

Syntax haben (nämlich SQL 97). Somit müsste nur die Stelle im Quelltext<br />

geän<strong>der</strong>t werden, die den Datenbanktreiber lädt und die Verb<strong>in</strong>dung zum Datenbankserver<br />

herstellt. Im e<strong>in</strong>fachsten Falle bedeutet das:<br />

1 DriverManager.getConnection(<br />

2 "jdbc:mysql://localhost:3306/dbname","user", "pass").connect();<br />

List<strong>in</strong>g 3.2: JDBC-Verb<strong>in</strong>dung zu e<strong>in</strong>em MySQL-Server <strong>in</strong> Java<br />

wird zu:<br />

1 DriverManager.getConnection(<br />

2 "jdbc:postgresql://localhost:5432/dbname","user", "pass").connect();<br />

List<strong>in</strong>g 3.3: JDBC-Verb<strong>in</strong>dung zu e<strong>in</strong>em PostgreSQL-Server <strong>in</strong> Java<br />

© Andreas Redmer — 29. September 2011 27


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

Die Unterschiede zwischen List<strong>in</strong>g 3.2 und List<strong>in</strong>g 3.3 s<strong>in</strong>d lediglich e<strong>in</strong> an<strong>der</strong>er<br />

Datenbanktreiber und Standardport für den Verb<strong>in</strong>dungsaufbau. Dies<br />

ist offensichtlich nur e<strong>in</strong>e m<strong>in</strong>imale und leicht vertretbare Verän<strong>der</strong>ung an dem<br />

Programm, das die Datenaufzeichung vornimmt. Für die Zeit <strong>der</strong> Umstellung<br />

ist auch e<strong>in</strong>e parallele Aufzeichnung auf zwei verschiedenen Datenbankservern<br />

denkbar. An dieser Stelle wird nochmal darauf h<strong>in</strong>gewiesen, dass die Verwendung<br />

e<strong>in</strong>es an<strong>der</strong>en Datenbankmodells (nicht relational) o<strong>der</strong> e<strong>in</strong>es an<strong>der</strong>en<br />

Speicherkonzeptes (JDO, Hibernate o. ä.) wesentlich größere Än<strong>der</strong>ungen erfor<strong>der</strong>t<br />

hätten.<br />

3.1.3. Wahl <strong>der</strong> Programmiersprache<br />

PostgreSQL bietet Unterstützung für SQL und PL/SQL (für PostgreSQL wird<br />

es PL/pgSQL genannt). PL/SQL ist die Eweiterung von SQL um Entscheidungen,<br />

Schleifen und benutzerdef<strong>in</strong>ierte Funktionen. Da<strong>durch</strong> ist die Sprache im<br />

Gegensatz zu SQL tur<strong>in</strong>gvollständig 8 . Je nach Kompilat br<strong>in</strong>gt PostgreSQL<br />

auch Unterstützung für PL/Perl, PL/Python und PL/Tcl mit. Dabei handelt<br />

es sich jeweils um e<strong>in</strong>e prozedurale Variation <strong>der</strong> Programmiersprache, die <strong>der</strong><br />

Kern-Distribution von PostgreSQL angehört. Derartige Funktionen werden im<br />

Quelltext auf den Server geladen und bei Bedarf <strong>in</strong>terpretiert und ausgeführt.<br />

Durch das e<strong>in</strong>fache Konzept des Server Programm<strong>in</strong>g Interface (SPI) von<br />

PostgreSQL ist es möglich auch Handler für eigene Programmiersprachen<br />

zu erstellen. So s<strong>in</strong>d beispielsweise <strong>in</strong> externen Projekten bereits PL/Java,<br />

PL/PHP, PL/Ruby und e<strong>in</strong>ige mehr entstanden. Diese bef<strong>in</strong>den sich <strong>in</strong> unterschiedlichen<br />

Entwicklungsstadien und haben auch unterschiedliche Performance.<br />

Auch <strong>der</strong> Aufwand <strong>der</strong> Installation kann, je nach Basissystem, für e<strong>in</strong>ige<br />

<strong>der</strong> Programmiersprachen höher se<strong>in</strong>. Letztendlich richtet sich es jedoch auch<br />

nach den Erfahrungen und Präferenzen <strong>der</strong> Softwareentwickler, die die UDF<br />

erstellen, welche Programmiersprache verwendet wird.<br />

Um die Laufzeiten <strong>der</strong> verschiedenen Möglichkeiten für UDF zu ermitteln<br />

wurde <strong>der</strong> Floyd-Warshall-Algorithmus implementiert. Dieser berechnet alle<br />

kürzesten Wege von jedem Knoten zu jedem an<strong>der</strong>en Knoten <strong>in</strong> e<strong>in</strong>em Graphen.<br />

Da <strong>in</strong> späteren Betrachtungen immer nur die kürzesten Wege von den<br />

Knoten zu den Gateways von Interesse se<strong>in</strong> werden, berechnet <strong>der</strong> Floyd-<br />

Warshall-Algorithmus viel mehr Informationen als eigentlich von Interesse s<strong>in</strong>d.<br />

Er läuft mit e<strong>in</strong>er Komplexität von O(n 3 ) auch relativ lange. Er wurde für die<br />

8 Das Hauptmerkmal dafür ist die Tatsache, dass SQL-Anfragen immer enden. Endlosschleifen<br />

gibt es <strong>in</strong> SQL-Anfragen nicht. Somit können mit SQL weniger Programme<br />

implementiert werden als mit tur<strong>in</strong>gvollständigen Sprachen.<br />

© Andreas Redmer — 29. September 2011 28


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

ersten Geschw<strong>in</strong>digkeitsvergleiche nur verwendet, weil er sich sehr e<strong>in</strong>fach implementieren<br />

lässt.<br />

1 ∀k = 1 TO n<br />

2 ∀i = 1 TO n<br />

3 ∀j = 1 TO n<br />

4 D i,j = m<strong>in</strong>(D i,j , D i,k + D k,j )<br />

List<strong>in</strong>g 3.4: Floyd-Warshall-Algorithmus <strong>in</strong> Pseudocode<br />

In List<strong>in</strong>g 3.4 ist <strong>der</strong> Floyd-Warshall-Algorithmus im Pseudocode abgebildet.<br />

Dabei steht n für die Anzahl <strong>der</strong> Knoten und D für die Distanzmatrix <strong>in</strong> <strong>der</strong><br />

<strong>der</strong> Graph gespeichert ist. Der Algorithmus arbeitet direkt auf dieser Matrix.<br />

D ist die E<strong>in</strong>gabe und nach <strong>der</strong> Berechnung auch die Ausgabe. Wie man leicht<br />

sieht, prüft <strong>der</strong> Algorithmus für jedes mögliche Paar von Knoten (i, j) ob <strong>der</strong><br />

Weg über e<strong>in</strong>en dritten Knoten (k) kürzer ist, als <strong>der</strong> bereits gefundene Weg<br />

zwischen i und j.<br />

Dieser Algorithmus wurde im Rahmen dieser Arbeit <strong>in</strong> SQL, PL/pgSQL,<br />

PL/Python und PL/Java implementiert. Dabei wurden die <strong>in</strong> Tabelle 3.1 angegebenen<br />

Laufzeiten gemessen.<br />

Programmiersprache Laufzeit<br />

SQL<br />

> 60 Sekunden<br />

PL/pgSQL<br />

> 60 Sekunden<br />

PL/Python<br />

6 Sekunden<br />

PL/Java<br />

0,25 Sekunden<br />

Tabelle 3.1.: Laufzeiten des Floyd-Warshall-Algorithmus<br />

SQL ist ke<strong>in</strong>e Programmiersprache und eignet sich somit meist nicht zur Implementierung<br />

von Algorithmen. Der Floyd-Warshall-Algorithmus ist jedoch so<br />

e<strong>in</strong>fach, dass er mit e<strong>in</strong>er SQL-Zeile berechnet werden kann. Das funktioniert,<br />

da die letzte Zeile des Algorithmus e<strong>in</strong>e e<strong>in</strong>fache Zuweisung ist und sich die<br />

drei Schleifen als Cross-Jo<strong>in</strong>s abbilden lassen. Im Anhang A.5 ist <strong>der</strong> zugehörige<br />

Quelltext abgebildet. Dennoch s<strong>in</strong>d für diese Ausführung umfangreiche<br />

Vorbereitungen notwendig. So muss die Menge <strong>der</strong> Knoten <strong>in</strong> e<strong>in</strong>e Tabelle<br />

geschrieben und dann die Distanzmatrix D <strong>in</strong> e<strong>in</strong>e Tabelle gespeichert werden.<br />

Es müssen Accessor- und Mutator-Funktionen geschrieben werden um<br />

auf e<strong>in</strong>en Wert <strong>in</strong> D (lesend und schreibend) zugreifen zu können. Weiterh<strong>in</strong><br />

muss e<strong>in</strong> Wert für unendlich (∞) def<strong>in</strong>iert (dabei s<strong>in</strong>d ”<br />

-1“ o<strong>der</strong> NULL beispielhafte<br />

Werte) und e<strong>in</strong>e M<strong>in</strong>imum-Funktion geschrieben werden, die diesen<br />

∞-Wert korrekt berücksichtigt.<br />

© Andreas Redmer — 29. September 2011 29


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

Die Laufzeit von mehr als e<strong>in</strong>er M<strong>in</strong>ute ist nicht akzeptabel, da jeweils e<strong>in</strong>e<br />

M<strong>in</strong>ute an aufgezeichneten Daten ausgewertet werden soll. Somit wäre die<br />

Datenaufzeichnung schneller als die <strong>Datenanalyse</strong> und <strong>der</strong> Versuch alle Daten<br />

zu analysieren würde nie enden. Weiterh<strong>in</strong> wurde im Abschnitt 2.3.1 angegeben,<br />

dass die bisherige Implementierung nur vier Sekunden dauert und diese<br />

optimiert werden soll. Die Laufzeit dieser SQL-Funktion ist so hoch, weil die<br />

Matrix D auf die m<strong>in</strong>destens 3 · n 3 mal zugegriffen wird (lesend und schreibend)<br />

hier nicht im Arbeitsspeicher liegt, son<strong>der</strong>n als persistente Tabelle <strong>in</strong> <strong>der</strong><br />

Datenbank.<br />

Mit PL/pgSQL ist die Implementierung auch nicht sehr performant implementierbar.<br />

Die Sprache unterstützt zwar mehrdimensionale Arrays und e<strong>in</strong>dimensionale<br />

Arrays mit variabler Größe, allerd<strong>in</strong>gs ke<strong>in</strong>e mehrdimensionalen<br />

Arrays mit variabler Größe. Da<strong>durch</strong> ist die Implementierung des Algorithmus<br />

eigentlich gar nicht möglich. Für dieses Problem gibt es e<strong>in</strong>ige Workarounds,<br />

die jedoch das mehrdimensionale Array bei jedem Zugriff komplett neu anlegen<br />

o<strong>der</strong> es <strong>in</strong> e<strong>in</strong>er temporären Tabelle <strong>in</strong> <strong>der</strong> Datenbank speichern. Da<strong>durch</strong><br />

ist auch hier ke<strong>in</strong>e Ausführung des Floyd-Warshall-Algorithmus unter e<strong>in</strong>er<br />

M<strong>in</strong>ute <strong>durch</strong>führbar.<br />

Mit PL/Python liegt die Ausführungszeit bei sechs Sekunden. Der dafür<br />

verwendete Quelltext f<strong>in</strong>det sich im Anhang A.6.<br />

Mit PL/Java wird die schnellste Ausführungszeit von 250 Millisekunden erreicht.<br />

Das Parsen o<strong>der</strong> Ausführen e<strong>in</strong>es Java-Programms ist im Allgeme<strong>in</strong>en<br />

nicht schneller als bei e<strong>in</strong>em Python-Programm. Der Unterschied ist, dass die<br />

UDF für PL/Python def<strong>in</strong>iert wird, <strong>in</strong>dem <strong>der</strong> Quelltext direkt auf den Server<br />

hochgeladen wird. Bei Bedarf (also bei jedem Aufruf <strong>der</strong> Funktion) wird <strong>der</strong><br />

Python-Quelltext geparst und ausgeführt. Bei PL/Java wird nur e<strong>in</strong> Verweis<br />

auf e<strong>in</strong>e Funktion <strong>in</strong> e<strong>in</strong>er class-Datei gespeichert. Jede PL/Java UDF besteht<br />

also für den Server aus nur e<strong>in</strong>er Zeile, wobei die tatsächliche Funktionalität<br />

<strong>in</strong> vorkompilierter Form <strong>in</strong> <strong>der</strong> class-Datei vorliegt.<br />

Dieses Resultat legt die Vermutung nahe, dass auch die an<strong>der</strong>en ad-hoc <strong>in</strong>terpretierten<br />

Sprachen ähnliche Defizite <strong>in</strong> <strong>der</strong> Ausführungszeit haben. Deshalb<br />

wurden PL/Tcl und PL/Perl nicht getestet. Auch PL/pgSQL würde wahrsche<strong>in</strong>lich<br />

auch <strong>in</strong> dieser Geschw<strong>in</strong>digkeit laufen, wenn die Arrays korrekt funktionieren<br />

würden. Es wurde also festgestellt, dass für die performanceoptimierte<br />

Programmierung e<strong>in</strong>e kompilierte UDF zu bevorzugen ist. Da C++ Funktionen<br />

(die es ja <strong>in</strong> MySQL auch gibt) aufgrund des hohen Entwicklungsaufwandes<br />

zuvor ausgeschlossen wurden, fiel die Wahl <strong>der</strong> Programmiersprache für diese<br />

Arbeit auf PL/Java.<br />

© Andreas Redmer — 29. September 2011 30


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

Zusätzlich zu <strong>der</strong> hohen Ausführungsgeschw<strong>in</strong>digkeit br<strong>in</strong>gt die Verwendung<br />

von Java noch diverse weitere Vorteile, da mit PL/Java, die gesamte Mächtigkeit<br />

von Java <strong>in</strong> die UDF von PostgreSQL e<strong>in</strong>gebracht wird.<br />

ˆ Es kann Eclipse als IDE verwendet werden,<br />

ˆ es kann JUnit als Test-Framework verwendet werden,<br />

ˆ es existiert e<strong>in</strong>e sehr gute und umfangreiche API und Dokumentation,<br />

ˆ die gesamte API (auch Dateisystemzugriffe, Socketverb<strong>in</strong>dungen, JDBC<br />

uvm.) kann <strong>in</strong> e<strong>in</strong>er UDF verwendet werden ohne <strong>durch</strong> e<strong>in</strong>en Security-<br />

Manager e<strong>in</strong>geschränkt zu se<strong>in</strong>,<br />

ˆ es gibt e<strong>in</strong>e Garbage-Collection<br />

ˆ und neben dem Just-In-Time Compiler wird <strong>der</strong> Code auch zur Laufzeit<br />

optimimiert.<br />

PL/Java-Funktionen sollen im Folgenden, <strong>der</strong> E<strong>in</strong>fachheit wegen, nur noch<br />

Java-Funktionen genannt werden. Der Ablauf für die Ausführung e<strong>in</strong>er Java-<br />

Funktion auf dem Datenbankserver ist <strong>in</strong> Abbildung 3.2 schematisch aufgezeigt.<br />

In Schritt 1 wird <strong>der</strong> Java-Quelltext <strong>in</strong> e<strong>in</strong>e java-Datei geschrieben. In<br />

Schritt 2 erfolgt die Übersetzung <strong>in</strong> Bytecode <strong>in</strong> e<strong>in</strong>e class-Datei. Diese wird <strong>in</strong><br />

Schritt 3 <strong>in</strong> e<strong>in</strong>e jar-Datei komprimiert. Mit Hilfe des Deployers von PL/Java<br />

wird <strong>in</strong> Schritt 4 e<strong>in</strong>e Kopie <strong>der</strong> jar-Datei auf dem Datenbankserver abgelegt.<br />

Zudem wird die Datei registriert und geladen. Da die Funktionen später als<br />

UDF dienen sollen, wurden sie alle als static deklariert und s<strong>in</strong>d mit dem Laden<br />

<strong>der</strong> Datei e<strong>in</strong>satzbereit. Globale Variablen, die <strong>in</strong> <strong>der</strong> Klasse dann ebenfalls<br />

als static deklariert wurden, werden an dieser Stelle <strong>in</strong>itialisiert. Derartige Variablen<br />

können dazu dienen, zwischen mehreren UDF zu kommunizieren o<strong>der</strong><br />

Zustände zu speichern. In Schritt 5 wird die UDF auf dem Server angelegt,<br />

also <strong>der</strong> Verweis <strong>der</strong> neuen Funktion auf die bestehende Java-Funktion. Java-<br />

Funktion und UDF müssen nicht den gleichen Namen haben, aber die Anzahl<br />

und Datentypen <strong>der</strong> Parameter und des Rückgabewertes müssen übere<strong>in</strong>stimmen.<br />

Schritt 6 ist die Ausführung <strong>der</strong> Funktion, die jetzt beliebig oft wie<strong>der</strong>holt<br />

werden kann. Wenn sich <strong>der</strong> Java-Quelltext än<strong>der</strong>t müssen die Schritte 1 bis<br />

4 erneut ausgeführt werden. Schritt 5 braucht nur erneut ausgeführt werden,<br />

wenn sich die Signatur <strong>der</strong> Java-Funktion und somit auch die Signatur <strong>der</strong><br />

UDF geän<strong>der</strong>t hat. Alles was <strong>in</strong> <strong>der</strong> Abbildung <strong>in</strong>nerhalb des DB-Servers e<strong>in</strong>gezeichnet<br />

wurde (JAR und UDF) ist persistent.<br />

Generell ist es freigestellt, welche Java Virtual Mach<strong>in</strong>e (JVM) benutzt wird.<br />

Die Entwickler von PL/Java geben an, dass auch <strong>der</strong> Gnu Java Compiler<br />

(GJC) benutzt werden kann, <strong>der</strong> den Java-Quelltext plattformabhängig <strong>in</strong> den<br />

© Andreas Redmer — 29. September 2011 31


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

1<br />

2<br />

Klasse.java<br />

Klasse.class<br />

class Klasse {<br />

static <strong>in</strong>t f(<strong>in</strong>t a) {<br />

return a+1;<br />

}<br />

}<br />

3<br />

file:///Klasse.jar<br />

DB Server<br />

4<br />

SELECT deploy ( ′ file:///Klasse.jar ′ );<br />

JAR<br />

5<br />

CREATE FUNCTION f (<strong>in</strong>t4 a)<br />

RETURNS <strong>in</strong>t4 AS ′ Klasse.f ′<br />

LANGUAGE java;<br />

UDF<br />

<strong>in</strong>t f(<strong>in</strong>t)<br />

<strong>in</strong>t f(<strong>in</strong>t)<br />

{<br />

. . .<br />

}<br />

6<br />

SELECT f (1);<br />

Abbildung 3.2.: Integration e<strong>in</strong>er Java UDF <strong>in</strong> PostgreSQL<br />

Masch<strong>in</strong>encode des Zielsystems kompiliert. Allerd<strong>in</strong>gs ist dies <strong>der</strong>zeitig als experimentell<br />

gekennzeichnet und wird deshalb im Rahmen dieser Arbeit nicht<br />

benutzt. Die im Rahmen dieser Arbeit verwendeten JVMs s<strong>in</strong>d <strong>in</strong> <strong>der</strong> Tabelle<br />

1.6 (Seite 11) angegeben.<br />

3.2. Schnittstellendef<strong>in</strong>ition<br />

In diesem Abschnitt wird die Schnittstelle beschrieben, die dem Datenanalysten<br />

zur Verfügung gestellt wird. Es wird also erklärt, wie die neu entwickelten<br />

UDF zu verwenden s<strong>in</strong>d. Weiterh<strong>in</strong> wird <strong>der</strong> <strong>in</strong>nere Aufbau <strong>der</strong> UDF und <strong>der</strong><br />

Java-Funktionen soweit def<strong>in</strong>iert, dass gute Erweiterbarkeit und Wie<strong>der</strong>verwendbarkeit<br />

des Quelltextes gegeben s<strong>in</strong>d. Zunächst werden alle UDF def<strong>in</strong>iert,<br />

die <strong>in</strong> e<strong>in</strong>er SQL Anweisung aufgerufen werden können.<br />

© Andreas Redmer — 29. September 2011 32


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

Funktionsname: shortestPaths<br />

Parameter: Timestamp<br />

Rückgabewert: e<strong>in</strong>e Menge von 2-Tupeln <strong>der</strong> Form: (Knoten,Vorgänger)<br />

SQL Beispielaufruf:<br />

SELECT * FROM shortestPaths("2011-01-31 07:30:02");<br />

Funktion: Berechnet die für die <strong>in</strong> Tabelle 1.1 (Seite 3) gegebenen<br />

Daten die kürzesten Wege von jedem Knoten zu e<strong>in</strong>em <strong>der</strong> Gateways,<br />

die zu e<strong>in</strong>em bestimmten Zeitpunkt gültig waren.<br />

Die Funktion shortestPaths ist e<strong>in</strong>e PL/pgSQL UDF, die weitere PL/Java<br />

UDF (Subfunktionen) aufruft. Sie löst das Problem <strong>der</strong> Abfrage <strong>der</strong> kürzesten<br />

Pfade (Problemklasse B - vgl. Abschnitt 1.4). Sie dient auch gleichzeitig<br />

als Vorlage um weitere UDF zu entwickeln, die mit den selben Subfunktionen<br />

weitere Probleme lösen, die <strong>in</strong> Abschnitt 1.4 beschrieben wurden (z.B. Flaschenhalsermittlung).<br />

Die Funktion shortestPaths soll den <strong>in</strong> Abbildung 3.3<br />

skizzierten Ablauf haben.<br />

Start<br />

E<strong>in</strong>gabe Timestamp<br />

truncateRootNodes<br />

addRootNode<br />

≈ 4 mal<br />

truncateL<strong>in</strong>ks<br />

≈ 800 mal<br />

addL<strong>in</strong>k<br />

≈ 800 mal<br />

run<br />

getResultSet<br />

Stop<br />

Abbildung 3.3.: Ablauf <strong>der</strong> shortestPaths Funktion<br />

Da die Gateways variabel se<strong>in</strong> können, werden diese nicht fest def<strong>in</strong>iert, son<strong>der</strong>n<br />

für jeden Zeitpunkt separat angegeben. Die Funktion addGateway fügt e<strong>in</strong><br />

Gateway <strong>in</strong> die Java-Klasse e<strong>in</strong>. Die Funktion addL<strong>in</strong>k fügt e<strong>in</strong>e Zeile aus dem<br />

Datenbestand für den gewählten Timestamp <strong>in</strong> die Java-Klasse e<strong>in</strong> und spei-<br />

© Andreas Redmer — 29. September 2011 33


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

chert diese dort <strong>in</strong> <strong>der</strong> <strong>in</strong>ternen Abbildung des Graphen. Da die Java-Klasse nur<br />

beim Deploy-Vorgang bzw. beim Neustart des Datenbankservers <strong>in</strong>itialisiert<br />

wird, muss es e<strong>in</strong>e Methode geben, um die <strong>in</strong>terne Speicherung des Graphen<br />

zu <strong>in</strong>itialisieren bzw. sie für die nächste Berechnung zurückzusetzen. Da sich die<br />

Knotenmenge (im Gegensatz zur Menge <strong>der</strong> Gateways) sehr stark verän<strong>der</strong>t,<br />

soll diese separat zurückgesetzt werden können. Deshalb gibt es die Funktion<br />

truncateGateways die alle Gateways aus <strong>der</strong> Java-Klasse löscht, sowie die<br />

Funktion truncateL<strong>in</strong>ks die alle zuvor mit addL<strong>in</strong>k e<strong>in</strong>gefügten Kanten im<br />

Graphen löscht. Die Knoten des Graphen werden nicht e<strong>in</strong>gefügt, son<strong>der</strong>n automatisch<br />

aus den Kanten ermittelt. Die Funktion run soll den Algorithmus<br />

zur Berechnung <strong>der</strong> kürzesten Wege ausführen. Die Funktion getResultSet<br />

gibt das Ergebnis zurück, welches unmittelbar auch die Rückgabe <strong>der</strong> Funktion<br />

shortestPaths se<strong>in</strong> soll. Die Funktionen run und getResultSet s<strong>in</strong>d bewusst<br />

getrennt um spätere Erweiterbarkeit zu gewährleisten. So kann zum Beispiel<br />

die Implementierung verschiedener Algorithmen später mit e<strong>in</strong>em Parameter<br />

<strong>der</strong> Funktion run gelöst werden. Wenn die Ausgabe um zusätzliche Spalten erweitert<br />

werden soll, kann <strong>der</strong> Rückgabewert von <strong>der</strong> Funktion getResultSet<br />

verän<strong>der</strong>t werden. Die genannten Subfunktionen s<strong>in</strong>d wie folgt def<strong>in</strong>iert.<br />

Funktionsname: addL<strong>in</strong>k<br />

Parameter: Knoten A, Knoten B, L<strong>in</strong>k Quality, Neighbour L<strong>in</strong>k Quality<br />

Rückgabewert: ke<strong>in</strong><br />

SQL Beispielaufruf:<br />

SELECT addL<strong>in</strong>k("192.168.0.1","192.168.0.2",1.0,0.9);<br />

Funktion: Fügt zwei Kanten <strong>in</strong> die <strong>in</strong>terne Repräsentation des Graphen<br />

<strong>in</strong> die Java-Klasse e<strong>in</strong>, welche die bidirektionale Verb<strong>in</strong>dung zwischen<br />

Knoten A und Knoten B im Graphen gewichtet repräsentieren.<br />

Wenn L<strong>in</strong>k Quality o<strong>der</strong> Neighbour L<strong>in</strong>ks Quality den Wert 0 hat, wird<br />

dafür entsprechend ke<strong>in</strong>e Kante e<strong>in</strong>gefügt. Die Knotennamen s<strong>in</strong>d Zeichenketten<br />

und die Qualitätswerte s<strong>in</strong>d Fließkommazahlen.<br />

© Andreas Redmer — 29. September 2011 34


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

Funktionsname: truncateL<strong>in</strong>ks<br />

Parameter: ke<strong>in</strong><br />

Rückgabewert: ke<strong>in</strong><br />

SQL Beispielaufruf:<br />

SELECT truncateL<strong>in</strong>ks();<br />

Funktion: Entfernt alle Kanten und Knoten aus <strong>der</strong> <strong>in</strong>ternen Repräsentation<br />

des Graphen <strong>in</strong> <strong>der</strong> Java Klasse, die zuvor mit addL<strong>in</strong>k e<strong>in</strong>gefügt<br />

wurden.<br />

Funktionsname: addGateway<br />

Parameter: Knotenname<br />

Rückgabewert: ke<strong>in</strong><br />

SQL Beispielaufruf:<br />

SELECT addGateway("192.168.0.1");<br />

Funktion: Fügt e<strong>in</strong> Gateway <strong>in</strong> die Java Klasse e<strong>in</strong>. Später wird von<br />

jedem Knoten im Graphen <strong>der</strong> kürzeste Weg zu e<strong>in</strong>em Gateway bestimmt.<br />

Funktionsname: truncateGateways<br />

Parameter: ke<strong>in</strong><br />

Rückgabewert: ke<strong>in</strong><br />

SQL Beispielaufruf:<br />

SELECT truncateGateways();<br />

Funktion: Entfernt alle Gateways aus <strong>der</strong> Java Klasse, die zuvor mit<br />

addGateway e<strong>in</strong>gefügt wurden.<br />

Funktionsname: run<br />

Parameter: ke<strong>in</strong><br />

Rückgabewert: ke<strong>in</strong><br />

SQL Beispielaufruf:<br />

SELECT run();<br />

Funktion: Startet die Berechnung <strong>der</strong> kürzesten Wege von jedem Knoten<br />

zu e<strong>in</strong>em Gateway. Es müssen zuvor Knoten, Kanten und m<strong>in</strong>destens<br />

e<strong>in</strong> Gateway mit den Funktionen addL<strong>in</strong>k und addGateway erstellt<br />

worden se<strong>in</strong>. Wenn mehrere Gateways gegeben s<strong>in</strong>d, wird <strong>der</strong> Weg zum<br />

nächstgelegenen Gateway als Ergebnis gespeichert.<br />

© Andreas Redmer — 29. September 2011 35


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

Funktionsname: getResultSet<br />

Parameter: ke<strong>in</strong><br />

Rückgabewert: e<strong>in</strong>e Tabelle mit den Spalten Knoten und Vorgänger<br />

SQL Beispielaufruf:<br />

SELECT * FROM getResultSet();<br />

Funktion: Gibt die zuvor mit run berechneten Pfade zurück. Für jeden<br />

Knoten wird <strong>der</strong> Vorgänger auf dem Pfad zum nächsten Gateway<br />

zurückgegeben. Für die Gateways werden ke<strong>in</strong>e Vorgänger zurückgegeben.<br />

Die Anzahl <strong>der</strong> zurückgegebenen Zeilen entspricht <strong>der</strong> Anzahl<br />

<strong>der</strong> e<strong>in</strong>gefügten Knoten abzüglich <strong>der</strong> Anzahl <strong>der</strong> Gateways.<br />

Alle zuvor genannten Funktionen (außer shortestPaths) sollen PL/Java<br />

UDF se<strong>in</strong>, die auf Methoden <strong>in</strong> e<strong>in</strong>er Java-Klasse verweisen. Die Java-Klasse<br />

wird nach dem <strong>in</strong> Abbildung 3.2 gezeigten Schema erstellt und geladen. Die<br />

Methoden s<strong>in</strong>d alle als public und static deklariert . Die Klasse soll m<strong>in</strong>destens<br />

das <strong>in</strong> Abbildung 3.4 <strong>in</strong> UML dargestellte Interface implementieren.<br />

-graph<br />

-rootNodes<br />

IShortestPaths<br />

+truncateRootNodes(): void<br />

+addRootNode(<strong>in</strong> Node:Str<strong>in</strong>g): void<br />

+truncateL<strong>in</strong>ks(): void<br />

+addL<strong>in</strong>k(<strong>in</strong> A:Str<strong>in</strong>g,<strong>in</strong> B:Str<strong>in</strong>g,<strong>in</strong> LQ:float,<strong>in</strong> NLQ:float): void<br />

+run(): void<br />

+getResultSet(): ResultSet<br />

Abbildung 3.4.: Interface für shortestPaths PL/Java (UML-<br />

Klassendiagramm)<br />

3.3. Möglichkeiten <strong>der</strong> Performancemessung<br />

Um die Performance <strong>der</strong> Implementierung des vorgestellten Konzeptes zu prüfen<br />

und zu verbessern, gibt es verschiedene praktische Arten <strong>der</strong> Zeitmessung,<br />

die zum E<strong>in</strong>satz kommen, um die Geschw<strong>in</strong>digkeit <strong>der</strong> Datenverarbeitung zu<br />

ermitteln. Diese werden hier vorgestellt, um die gemessenen Zeiten nachvollziehbar<br />

zu machen.<br />

© Andreas Redmer — 29. September 2011 36


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

Die Dauer e<strong>in</strong>er SQL-Anweisung, die mit e<strong>in</strong>er graphischen Oberfläche an<br />

den SQL-Server gesendet wird, wird meist zusätzlich zum Ergebnis angezeigt.<br />

Im Rahmen dieser Arbeit wurde pgAdm<strong>in</strong> [27] verwendet, welches nach je<strong>der</strong><br />

Abfrage die benötigte Zeit <strong>in</strong> Millisekunden anzeigt. Wenn ke<strong>in</strong>e graphische<br />

Oberfläche vorhanden ist und die SQL-Abfragen direkt an <strong>der</strong> Shell an den<br />

SQL-Server gesendet werden, kann die Zeit <strong>durch</strong> den L<strong>in</strong>ux/Unix Befehl time<br />

gemessen werden. E<strong>in</strong> solcher Aufruf könnte beispielsweise wie folgt aussehen:<br />

time echo ’SELECT count(*) FROM l<strong>in</strong>ks;’ | psql<br />

Diese Zeitmessung umfasst nicht nur die Ausführung <strong>der</strong> SQL-Anweisung selbst,<br />

son<strong>der</strong>n auch das Senden <strong>der</strong> Anweisung an den Server (unter Umständen über<br />

e<strong>in</strong>e Netzwerkverb<strong>in</strong>dung) und die Ausgabe des Ergebnisses. Die Ausgabe des<br />

Ergebnisses auf <strong>der</strong> Konsole ist sehr langsam und verfälscht die Messung stark.<br />

Wenn also sehr viele Zeilen zurückgegeben werden, ist das gemessene Ergebnis<br />

unbrauchbar. Das e<strong>in</strong>fache Zählen <strong>der</strong> Zeilen im Ergebnis vermeidet dieses<br />

Problem. So dauert die Ausführung des Befehls:<br />

time echo ’SELECT * FROM l<strong>in</strong>ks;’ | psql<br />

deutlich länger als <strong>der</strong> vorher genannte.<br />

Jedoch verbraucht auch das Zählen <strong>der</strong> Zeilen e<strong>in</strong> wenig Zeit. Dies kann<br />

vermieden werden, <strong>in</strong>dem das Ergebnis <strong>der</strong> Abfrage <strong>in</strong> e<strong>in</strong>e Datei umgeleitet<br />

wird. Dies kann bei großen Dateien zu Problemen führen und hängt von <strong>der</strong><br />

Geschw<strong>in</strong>digkeit <strong>der</strong> Festplatte ab. Außerdem verwalten verschiedene Dateisystem<br />

große Dateien auf verschiedene Weisen. Auch <strong>der</strong> Füllstand <strong>der</strong> Partition<br />

und die Fragmentierung <strong>der</strong> neuen Datei wirkt sich auf die Zeitmessung aus.<br />

Somit bietet die Umleitung <strong>in</strong> e<strong>in</strong>e Datei, die nicht <strong>in</strong> e<strong>in</strong>em Dateisystem auf<br />

e<strong>in</strong>em physikalischen Speicher abgelegt ist, die genaueste Messung. E<strong>in</strong> Beispiel<br />

dafür sieht so aus:<br />

time echo ’SELECT * FROM l<strong>in</strong>ks;’ | psql > /dev/null<br />

Bei <strong>der</strong> Verwendung e<strong>in</strong>er graphischen Oberfläche ist die Ausgabe von sehr vielen<br />

(> 1000000) Zeilen nicht mehr möglich. Denn dabei wird pro Ergebniszeile,<br />

e<strong>in</strong>e Zeile <strong>in</strong> e<strong>in</strong>em Listenfeld <strong>der</strong> verwendeten GUI angelegt. Derartig große<br />

Listenfel<strong>der</strong> s<strong>in</strong>d nicht vorgesehen. Der Versuch e<strong>in</strong>e solche Ausgabe anzuzeigen<br />

bricht nach mehreren Stunden Ausführungszeit ab (da <strong>der</strong> Arbeitsspeicher<br />

voll ist) o<strong>der</strong> dauert aufgrund <strong>der</strong> Auslagerung von Arbeitsspeicher so lange,<br />

dass die Ausführung nicht effizient möglich ist. Wenn die graphische Benut-<br />

© Andreas Redmer — 29. September 2011 37


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

zeroberfläche ke<strong>in</strong>e Möglichkeit bietet, das Ergebnis <strong>in</strong> e<strong>in</strong>e Datei umzuleiten,<br />

kann <strong>der</strong> SQL-Server mit dem COPY-Befehl die Ausgabe <strong>in</strong> e<strong>in</strong>er Datei ablegen.<br />

Beispielsweise kann man die SQL-Anweisung<br />

COPY (SELECT * FROM l<strong>in</strong>ks) TO /dev/null ;<br />

verwenden. Die Ausgabe <strong>in</strong> e<strong>in</strong>e Datei erfolgt dann jedoch auf dem Server und<br />

nicht auf dem Client (<strong>der</strong> Zeitaufwand für die Übertragung des Ergebnisses<br />

zum Client entfällt also).<br />

Die gemessene Performance ist immer abhängig von <strong>der</strong> aktuellen Auslastung<br />

des Systems. Es spielt also <strong>der</strong> Füllstand des Arbeitsspeichers und des<br />

Auslagerungsspeichers, sowie die aktuelle CPU-Last e<strong>in</strong>e Rolle. Auch die Größe<br />

und <strong>der</strong> Füllstand des CPU-Caches spielen beson<strong>der</strong>s auf dedizierten Datenbankservern<br />

e<strong>in</strong>e Rolle. Durch die Nutzung e<strong>in</strong>es CPU-Caches wird e<strong>in</strong>e UDF<br />

im ersten Durchlauf deutlich langsamer ausgeführt als <strong>in</strong> den folgenden Durchläufen.<br />

Die komplexen Zusammenhänge zwischen den Hardwarekomponenten<br />

und den Ausführungszeiten sollen an dieser Stelle nicht weiter erläutert werden.<br />

Alle Laufzeiten, die im Rahmen dieser Arbeit angeben werden s<strong>in</strong>d Durchschnittszeiten.<br />

Insbeson<strong>der</strong>e bei Messungen im Millisekundenbereich wurden<br />

jeweils mehrere Millionen Testläufe <strong>durch</strong>geführt und jeweils die <strong>durch</strong>schnittliche<br />

Ausführungszeit angegeben.<br />

Unabhängig von den UDF können auch die be<strong>in</strong>halteten Java-Programme<br />

e<strong>in</strong>er Zeitmessung unterzogen werden. Dazu wurden im Rahmen dieser Arbeit<br />

die folgenden drei Arten verwendet:<br />

ˆ Verwendung <strong>der</strong> JUnit<br />

ˆ Verwendung von System.currentTimeMillis()<br />

ˆ Verwendung von System.nanoTime()<br />

Die JUnit [21] ist e<strong>in</strong> Testframework für Java, das es ermöglicht automatisierte<br />

Tests für Java-Programme <strong>durch</strong>zuführen. Auch für die <strong>in</strong> dieser Arbeit<br />

verwendeten Algorithmen (und <strong>der</strong>en Subfunktionen) wurden weitreichende<br />

Tests geschrieben. Diese wurden <strong>in</strong> e<strong>in</strong>er Testsuite zusammengefasst und nach<br />

je<strong>der</strong> Än<strong>der</strong>ung im Quelltext ausgeführt. Da die JUnit nach jedem Test auch<br />

die Ausführungszeit angibt, wurde e<strong>in</strong>e weitere Testsuite für Performancetests<br />

angelegt. Diese Tests führen zeitkritische Funktionen e<strong>in</strong>mal o<strong>der</strong> mehrmals<br />

aus, um Performanceverän<strong>der</strong>ungen nach je<strong>der</strong> Än<strong>der</strong>ung im Quelltext evaluieren<br />

zu können.<br />

Die Java-Funktion System.currentTimeMillis() gibt die aktuelle Systemzeit<br />

<strong>in</strong> Millisekunden aus, also die Anzahl <strong>der</strong> vergangenen Millisekunden seit<br />

© Andreas Redmer — 29. September 2011 38


3. Vorbetrachtungen e<strong>in</strong>er hochperformanten Lösung<br />

dem 01.01.1970 00:00 Uhr. Damit lässt sich die Ausführungszeit von Quelltext<br />

relativ genau messen. Die Funktion System.nanoTime() funktioniert analog,<br />

jedoch mit <strong>der</strong> Angabe des Timestamps <strong>in</strong> Nanosekunden. Dazu ist zu bemerken,<br />

dass die Granularität zwar nanosekundengenau ist, jedoch nicht die<br />

Präzision [26]. Die Präzision hängt vom Betriebssystem ab, auf dem die JVM<br />

ausgeführt wird. In <strong>der</strong> Javadokumentation [26] wird <strong>der</strong> <strong>in</strong> List<strong>in</strong>g 3.5 angegebene<br />

Java-Quelltext zur Zeitmessung vorgeschlagen. Dort f<strong>in</strong>den sich auch<br />

weitere Informationen zu diesen beiden API-Funktionen.<br />

1 long startTime = System.nanoTime();<br />

2 // ... the code be<strong>in</strong>g measured ...<br />

3 long estimatedTime = System.nanoTime() - startTime;<br />

List<strong>in</strong>g 3.5: Zeitmessung für e<strong>in</strong> Java-Programm<br />

Beson<strong>der</strong>e Beachtung, bei <strong>der</strong> Zeitmessung für Java-Programme, ist dem<br />

JIT-Compiler zu schenken. Java-Programme werden grundsätzlich von Quelltext<br />

<strong>in</strong> Bytecode übersetzt. Bytecode ist e<strong>in</strong>e plattformunabhängige Form des<br />

Java-Programms. Auf dem Zielsystem wird <strong>der</strong> Bytecode <strong>in</strong> Hotspots unterteilt<br />

und je<strong>der</strong> Hotspot wird unmittelbar vor <strong>der</strong> Ausführung kompiliert. So<br />

kann beispielsweise <strong>der</strong> Inhalt e<strong>in</strong>er Schleife o<strong>der</strong> e<strong>in</strong>er Funktion e<strong>in</strong> Hotspot<br />

se<strong>in</strong>. Das führt dazu, dass die erste Ausführung im ersten Durchlauf deutlich<br />

länger dauert als alle weiteren Durchläufe. Zusätzlich hat die JVM e<strong>in</strong>en<br />

JIT-Optimierer, <strong>der</strong> den Programmablauf zur Laufzeit mit Compilertechniken<br />

optimiert. So kann z. B. <strong>der</strong> IF- und <strong>der</strong> ELSE-Zweig e<strong>in</strong>er Entscheidung zur<br />

Laufzeit vertauscht werden, wenn <strong>der</strong> Optimierer feststellt, dass die Bed<strong>in</strong>gung<br />

öfter falsch ist als wahr. Ähnliche Mechanismen gibt es auch auf Hardwareebene<br />

<strong>in</strong> mo<strong>der</strong>nen CPUs.<br />

Das führt dazu, dass die gemessenen Zeiten <strong>der</strong> ersten Durchläufe ignoriert<br />

werden sollten. Danach kann wie<strong>der</strong> mit den <strong>durch</strong>schnittlichen Ausführungszeiten<br />

gearbeitet werden. Die <strong>durch</strong>schnittlichen Ausführungszeiten s<strong>in</strong>d später<br />

relevant um e<strong>in</strong>e Vorhersage für die Dauer großer Datenanalyen machen zu<br />

können.<br />

© Andreas Redmer — 29. September 2011 39


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

Im praktischen Teil dieser Arbeit wurde das zuvor vorgestellte Konzept implementiert<br />

und auf verschiedenste Arten optimiert. Diese Optimierungen lassen<br />

sich generell <strong>in</strong> algorithmische Optimierungen (Abschnitt 4.1), Optimierungen<br />

am Programmierstil (Abschnitt 4.2) und Parallelisierung (Abschnitt 4.3)<br />

e<strong>in</strong>teilen. In diesem Kapitel werden sie genauer erläutert und die Gründe für<br />

ihre Verwendung o<strong>der</strong> Nichtverwendung genannt. Am Ende dieses Kapitels,<br />

werden alle <strong>durch</strong>geführten Optimierungen nochmals tabellarisch zusammengefasst<br />

(Abschnitt 4.4). Als Optimierung ist hier immer e<strong>in</strong>e Verän<strong>der</strong>ung des<br />

Softwareprogramms geme<strong>in</strong>t, die den Zeitaufwand für dessen Ausführung reduziert.<br />

Weitere Optimierungen (wie z. B. die Verwendung schnellerer Hardware)<br />

wären ebenfalls möglich gewesen, wurden jedoch im Rahmen dieser Arbeit<br />

nicht untersucht.<br />

4.1. Algorithmische Optimierungen<br />

4.1.1. Optimierung des Dijkstra-Algorithmus<br />

Im Abschnitt 1.4 wurde bereits festgestellt, dass e<strong>in</strong>e effiziente Implementierung<br />

des Dijkstra-Algorithmus nötig ist um die Ziele dieser Arbeit zu erreichen.<br />

Im Abschnitt 2.3.1 (List<strong>in</strong>g 2.2, Seite 18) wurde <strong>der</strong> Dijkstra-Algorithmus beschrieben.<br />

Der große Teil des Zeitaufwandes für den Algorithmus liegt dar<strong>in</strong>,<br />

die unbenutzten von den bereits benutzten Knoten zu trennen, von den unbenutzten<br />

Knoten den Nächstgelegenen zu f<strong>in</strong>den und von diesem dann wie<strong>der</strong><br />

alle unbenutzten Nachbarn zu betrachten. Diese Form <strong>der</strong> Exploration kann<br />

sehr zeitaufwändig se<strong>in</strong>, wenn die dafür benutzen Datentypen nicht korrekt<br />

gewählt werden. Der Dijkstra-Algorithmus wurde mit <strong>der</strong> Komplexität<br />

O(n + n 2 + m)<br />

angegeben. Dabei ist das erste n lediglich die Initialisierung, die sich nicht<br />

optimieren lässt. Es wird dafür gesorgt, dass alle Knoten <strong>in</strong> die vorgesehenen<br />

abstrakten Datentypen e<strong>in</strong>gefügt, Anfangswerte auf 0 gesetzt bzw. noch<br />

nicht ermittelte Abstände mit ∞ ( ”<br />

unendlich“) <strong>in</strong>itialisiert werden. Je nach<br />

© Andreas Redmer — 29. September 2011 40


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

verwendeter Programmiersprache und abhängig von dem Wert, den man für<br />

∞ festlegt (vgl. Abschnitt 4.2.4), ist die Initialisierung optional. Somit wird<br />

nur die Optimierung des Terms<br />

betrachtet.<br />

O(n 2 + m)<br />

Für die Verwaltung <strong>der</strong> noch nicht behandelten Knoten, wurde die Prioritätswarteschlange<br />

Q verwendet. Auf dieser Warteschlange, müssen verschiedene<br />

Operationen (wie z. B. E<strong>in</strong>fügen, M<strong>in</strong>imum f<strong>in</strong>den, Löschen) ausgeführt<br />

werden. Im List<strong>in</strong>g 2.2 (Seite 18) wurde zunächst angenommen, dass die Warteschlange<br />

mit e<strong>in</strong>er e<strong>in</strong>fach verketteten Liste implementiert ist. Damit s<strong>in</strong>d alle<br />

Operationen (außer Löschen) mit dem Aufwand O(1) möglich. Zum Löschen<br />

wird jedoch l<strong>in</strong>eare Zeit (O(n)) benötigt. Während das F<strong>in</strong>den des M<strong>in</strong>imums,<br />

mittels e<strong>in</strong>er zusätzlichen Variable, immer <strong>in</strong> konstanter Zeit ausgeführt werden<br />

kann, än<strong>der</strong>n sich die Zeitkomplexitäten für die an<strong>der</strong>en Operationen je<br />

nach verwendeter Datenstruktur. So ist es beispielsweise möglich e<strong>in</strong>en Heap<br />

zu verwenden und damit e<strong>in</strong>en Zeitaufwand für jede Operation von O(log(n))<br />

zu haben. Der Zeitaufwand für den Algorithmus von Dijkstra läge damit bei<br />

O((n + m) · log(n)).<br />

Im Jahre 1987 fanden Fredman und Tarjan e<strong>in</strong>e Möglichkeit Fibonacci-Heaps<br />

sehr effizient im Dijkstra-Algorithmus zu verwenden [16]. Mit e<strong>in</strong>em Fibonacci-<br />

Heap ist das E<strong>in</strong>fügen <strong>in</strong> konstanter Zeit und das Löschen <strong>in</strong> O(log(n)) möglich.<br />

Weiterh<strong>in</strong> stellten sie für den Fibonacci-Heap verbesserte Laufzeiten <strong>in</strong><br />

<strong>der</strong> amortisierten Laufzeitanalyse fest, wobei <strong>der</strong> Worst-Case O(log(n)) nur<br />

sehr selten e<strong>in</strong>trat. Für die meisten Durchläufe im gesamten Dijkstra-Ablauf<br />

s<strong>in</strong>d die Kosten für das Löschen ebenfalls O(1). Nach dieser amortisierten Betrachtungsweise<br />

ist die Laufzeit für den Dijkstra-Algorithmus auf<br />

O(n · log(n) + m)<br />

gesunken. Die Verwendung von Fibonacci-Heaps ist heute die gängigste Methode<br />

um kürzeste Pfade mit dem Dijkstra-Algorithmus zu berechnen.<br />

Die Java-Klasse PriorityQueue verwendet lediglich e<strong>in</strong>e verkettete Liste<br />

(bzw. e<strong>in</strong> dynamisch wachsendes Array) und ist somit für die Verwendung im<br />

Dijkstra-Algorithmus nicht optimal. Die freie Graphen-Bibliothek JGraphT<br />

[23] br<strong>in</strong>gt jedoch e<strong>in</strong>e Implementierung des Dijkstra-Algorithmus und e<strong>in</strong>e<br />

FibonacciHeap-Klasse mit sich. Der Dijkstra-Algorithmus wurde im Rahmen<br />

© Andreas Redmer — 29. September 2011 41


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

dieser Arbeit neu implementiert, weil er sehr e<strong>in</strong>fach ist und weil die Implementierung<br />

von JGraphT nicht unverän<strong>der</strong>t übernommen werden konnte. So<br />

berechnete JGraphT nur den Weg von e<strong>in</strong>em Startknoten zu e<strong>in</strong>em Zielknoten<br />

und bricht die Berechnung dann ab. Wie aber im Abschnitt 1.4 schon erwähnt,<br />

darf <strong>der</strong> Dijkstra <strong>in</strong> unserem Projekt nicht abbrechen, son<strong>der</strong>n muss immer die<br />

kürzesten Wege von e<strong>in</strong>em Startknoten zu allen an<strong>der</strong>en Knoten f<strong>in</strong>den. Die<br />

Klasse FibonacciHeap wurde jedoch zunächst <strong>in</strong> diese Arbeit übernommen.<br />

Die Verwendung des Fibonacci-Heaps brachte für die Graphen, die <strong>in</strong> dieser<br />

Arbeit Verwendung f<strong>in</strong>den, signifikante Unterschiede und wurde deshalb <strong>in</strong> <strong>der</strong><br />

endgültigen Version beibehalten.<br />

4.1.2. Optimierung des Graphen<br />

Unabhängig vom verwendeten Graphenalgorithmus kann <strong>der</strong> Graph <strong>in</strong> vielen<br />

Fällen vorher optimiert werden, um die Ausführung des Algorithmus zu beschleunigen.<br />

Dies geschieht <strong>durch</strong> das Streichen von Kanten. Im Abschnitt<br />

1.3 wurde bereits erklärt, dass die Daten auch teilweise mehrere Kanten zwischen<br />

zwei Knoten aufzeigen, die <strong>durch</strong>aus von unterschiedlicher Qualität se<strong>in</strong><br />

können. Das hängt damit zusammen, dass die Aufzeichnungen von Werten<br />

abhängen, die von unterschiedlichen Stellen im Netzwerk zu unterschiedlichen<br />

Zeiten aufgezeichnet wurden. Der Dijkstra-Algorithmus würde <strong>in</strong> jedem Fall<br />

die bessere Verb<strong>in</strong>dung wählen und die schlechteren Kanten ignorieren. Dennoch<br />

wird <strong>der</strong> Algorithmus da<strong>durch</strong> langsamer, denn die Anzahl <strong>der</strong> Kanten m<br />

ist e<strong>in</strong> Summand <strong>in</strong> <strong>der</strong> Komplexität des Algorithmus (O(n · log(n) + m)). Wie<br />

man schnell erkennt, ist die da<strong>durch</strong> erreichbare Optimierung nur sehr ger<strong>in</strong>g,<br />

da m ke<strong>in</strong>en sehr großen E<strong>in</strong>fluss auf die Laufzeit hat.<br />

Das Streichen von Knoten ist theoretisch auch möglich um den Dijkstra<br />

zu beschleunigen, jedoch nur wenn es tatsächlich unerreichbare Knoten (also<br />

Knoten zu denen ke<strong>in</strong>e Kante führt) gibt. Das ist für die hier aufgezeichneten<br />

Daten nie <strong>der</strong> Fall, da e<strong>in</strong>e Zeile <strong>in</strong> <strong>der</strong> Datenaufzeichnung immer zwei Knoten<br />

und die Kanten zwischen Ihnen repräsentiert. Somit führt zu jedem Knoten<br />

m<strong>in</strong>destens e<strong>in</strong>e Kante.<br />

Im Rahmen dieser Arbeit wurde das Streichen von Kanten direkt mit <strong>in</strong><br />

die Initialisierung des Dijkstra-Algorithmus <strong>in</strong> Java implementiert. Das Entfernen<br />

<strong>der</strong> falschen Kanten, kann im Vorfeld auch <strong>durch</strong> e<strong>in</strong>e SQL-Anweisung<br />

geschehen. Dies wurde jedoch aus den folgenden Gründen nicht getan:<br />

ˆ es dauert bei <strong>der</strong> Anwendung auf den gesamten Datenbestand sehr lange,<br />

© Andreas Redmer — 29. September 2011 42


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

ˆ die Determ<strong>in</strong>ierung <strong>der</strong> ”<br />

gültigen“ Kante (M<strong>in</strong>imum, Maximum o<strong>der</strong><br />

Durchschnitt - vgl. Abschnitt 1.3) bleibt variabel,<br />

ˆ die Orig<strong>in</strong>aldaten sollen <strong>durch</strong> die Analyse nicht verän<strong>der</strong>t werden (die<br />

UDF müssten also mit e<strong>in</strong>er Kopie arbeiten) und<br />

ˆ die UDF sollen auch zukünftig, je<strong>der</strong>zeit auf unbearbeiteten Daten funktionieren.<br />

Die Performance wurde <strong>durch</strong> das Streichen <strong>der</strong> Kanten nur sehr ger<strong>in</strong>gfügig<br />

verbessert.<br />

4.1.3. Die ”<br />

General-Gateway-Strategie“<br />

E<strong>in</strong> allgeme<strong>in</strong>es Problem ist, dass <strong>der</strong> Dijkstra-Algorithmus immer k mal ausgeführt<br />

werden muss, wobei k die Anzahl <strong>der</strong> Gateways ist. Es müssen immer<br />

die kürzesten Wege zu jedem an<strong>der</strong>en Knoten, ausgehend von jedem e<strong>in</strong>zelnen<br />

Gateway ermittelt und anschließend <strong>der</strong> kürzeste Weg zu e<strong>in</strong>em Gateway ausgegeben<br />

werden. Derzeit bef<strong>in</strong>den sich vier Gateways im Opennet, jedoch ist<br />

diese Anzahl variabel. Da <strong>der</strong> Graph für jeden Timestamp konstant vorgegeben<br />

ist, stellt sich die Frage, ob man den Ablauf nicht so modifizieren kann,<br />

dass e<strong>in</strong>mal berechnete Wege nicht immer wie<strong>der</strong> berechnet werden müssen.<br />

A<br />

1 2<br />

B<br />

M<br />

1<br />

8<br />

G 1 G 2<br />

2<br />

C<br />

1<br />

2<br />

7<br />

1<br />

D<br />

K<br />

13<br />

7<br />

H<br />

6<br />

G 4<br />

10<br />

G 3<br />

3<br />

3<br />

F<br />

E<br />

Abbildung 4.1.: E<strong>in</strong> beispielhafter unmodifizierter Graph<br />

Abbildung 4.1 zeigt e<strong>in</strong>en <strong>Netzwerkgraphen</strong> mit vier Gateways (G 1 , G 2 , G 3<br />

und G 4 ). Der Dijkstra-Algorithmus muss dort vier mal gestartet werden, mit<br />

jeweils e<strong>in</strong>em <strong>der</strong> Gateways als Startpunkt. Zum Schluss werden alle vier kürzesten<br />

Wege, die pro Knoten zu e<strong>in</strong>em Gateway führen, verglichen und nur<br />

<strong>der</strong> kürzeste Weg zurückgegeben. Je<strong>der</strong> Durchlauf betrachtet den gesamten<br />

© Andreas Redmer — 29. September 2011 43


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

Graphen. Unter an<strong>der</strong>em würde also vier mal festgestellt werden, dass beispielsweise<br />

<strong>der</strong> kürzeste Weg von C nach E immer die Kosten 2 hat und immer<br />

über D geht. Es werden also vielfach die gleichen Berechnungen ausgeführt.<br />

Im Rahmen dieser Arbeit wurde e<strong>in</strong>e Idee entwickelt, die dieses Problem löst<br />

und somit ke<strong>in</strong>e mehrfachen Berechnungen (auf dem selben Graphen) mehr<br />

<strong>durch</strong>geführt werden müssen. Grundlage dafür ist die Tatsache, dass die kürzeste<br />

Route zu e<strong>in</strong>em Gateway nie über e<strong>in</strong> an<strong>der</strong>es Gateway verläuft. Der<br />

theoretische Idealzustand wäre, wenn sich alle Gateways an e<strong>in</strong>em zentralen<br />

Punkt des Netzwerks bef<strong>in</strong>den, wenn zwischen den Gateways ke<strong>in</strong>e weiteren<br />

Knoten wären und die Gateways sich untere<strong>in</strong>an<strong>der</strong> alle mit e<strong>in</strong>em Kostenaufwand<br />

von 0 erreichen können. In diesem Fall könnte man alle Gateways zu<br />

e<strong>in</strong>em Gateway zusammenfassen und bräuchte nur noch die Wege zu diesem<br />

vere<strong>in</strong>heitlichten Gateway zu suchen.<br />

A<br />

1 2<br />

B<br />

M<br />

1<br />

8<br />

0<br />

G 1 G 2<br />

2<br />

C<br />

1<br />

2<br />

7<br />

0<br />

0<br />

1<br />

D<br />

K<br />

13<br />

7<br />

H<br />

6<br />

G 4<br />

0<br />

G 3<br />

3<br />

3<br />

F<br />

E<br />

Abbildung 4.2.: E<strong>in</strong> Netzwerkgraph mit generalisiertem Gateway<br />

In <strong>der</strong> Praxis ist das natürlich nicht <strong>der</strong> Fall, da die Gateways beliebig im<br />

Netzwerk verteilt se<strong>in</strong> können. Da jedoch bereits festgestellt wurde, dass die<br />

direkten Wege zwischen den Gateways irrelevant s<strong>in</strong>d (da sie nie zu e<strong>in</strong>em <strong>der</strong><br />

gesuchten kürzesten Pfade gehören werden und da für Gateways selbst auch<br />

ke<strong>in</strong> Pfad gesucht werden muss), können die irrelevanten Wege <strong>durch</strong> Nullkanten<br />

9 ersetzt werden. Dabei ist es egal, ob vorher e<strong>in</strong>e direkte o<strong>der</strong> <strong>in</strong>direkte<br />

Verb<strong>in</strong>dung zwischen den Gateways existiert hat o<strong>der</strong> nicht. Das bedeutet,<br />

dass das gesamte Netzwerk auch aus mehreren getrennten Teilnetzwerken be-<br />

9 Exakt wäre hier die Bezeichnung Nullelementkante, was bedeutet, dass jeweils das Nullelement<br />

aus <strong>der</strong> Metrik als Kantengewicht verwendet wird. Bei e<strong>in</strong>er multiplikativen Metrik<br />

wäre es beispielsweise e<strong>in</strong>e Kante mit dem Kostenfaktor 1.<br />

© Andreas Redmer — 29. September 2011 44


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

stehen kann, wobei <strong>in</strong> jedem Teilnetzwerk m<strong>in</strong>destens e<strong>in</strong> Gateway existiert.<br />

Es ist auch egal wie viele Nullkanten e<strong>in</strong>gefügt werden, allerd<strong>in</strong>gs muss jedes<br />

Gateway jedes an<strong>der</strong>e Gateway mit den Kosten 0 erreichen. In Abbildung 4.2<br />

wurden diese Nullkanten h<strong>in</strong>zugefügt und das generalisierte (zusammengefasste)<br />

Gateway mit e<strong>in</strong>er gestrichelten L<strong>in</strong>ie gekennzeichnet.<br />

Mit dem neu entstandenen Graph wird nun wie folgt verfahren:<br />

1. Wähle e<strong>in</strong>en beliebiges Gateway als Startknoten.<br />

2. Führe damit den Dijkstra-Algorithmus e<strong>in</strong>mal aus.<br />

3. Streiche bei <strong>der</strong> Rückgabe des Ergebnisses alle Wege über Nullkanten 10 .<br />

Beispielsweise wählt man <strong>in</strong> Abbildung 4.2 G 1 als Startknoten. Ausgabe ist<br />

zunächst <strong>der</strong> kürzeste Weg von jedem Knoten zu G 1 . So erhält man unter<br />

an<strong>der</strong>em die Route<br />

0 2<br />

G 1 −→ G 2 −→ C −→ 1<br />

D<br />

zum Knoten D. Am Anfang je<strong>der</strong> Route können sich nun <strong>durch</strong>aus mehrere<br />

Gateways bef<strong>in</strong>den, die über Nullwege zum gesuchten Gateway führen. Alle<br />

Nullwege und die davor liegenden Gateways müssen vor <strong>der</strong> Ausgabe gestrichen<br />

werden, da dies die künstlich erzeugten Wege s<strong>in</strong>d. Als Ergebnis erhält man<br />

dann die Route<br />

2<br />

G 2 −→ C −→ 1<br />

D<br />

zum Knoten D. Dies ist <strong>der</strong> kürzeste Weg vom Knoten D zu e<strong>in</strong>em Gateway.<br />

Diese Strategie soll im folgenden General-Gateway-Strategie genannt werden<br />

und wurde auch an den entsprechenden Stellen im Quelltext so benannt.<br />

Die Strategie liefert das gleiche Ergebnis, wie die Ausführung von vier Dijkstra-<br />

Durchläufen und <strong>der</strong> anschließenden Auswahl des nächstgelegenen Gateways.<br />

Zwar muss <strong>der</strong> kürzeste Weg nicht immer e<strong>in</strong>deutig bestimmt se<strong>in</strong>, aber es ist <strong>in</strong><br />

realen Netzwerken unwahrsche<strong>in</strong>lich, dass zwei Gateways den selben und besten<br />

Weg <strong>in</strong>s Internet bieten. Wenn dies auftreten würde, wären die Ergebnisse<br />

nicht falsch, aber die Strategien nicht mehr vergleichbar. Testweise wurden beide<br />

Strategien <strong>in</strong> dieser Arbeit für den gesamten Datenbestand gegene<strong>in</strong>an<strong>der</strong><br />

getestet und es wurden ke<strong>in</strong>e Unterschiede festgestellt 11 .<br />

Aus Sicht <strong>der</strong> Komplexitätstheorie verbessert die General-Gateway-Strategie<br />

den Ablauf um e<strong>in</strong>en konstanten Faktor (im Beispiel um den Faktor 4). Der<br />

Teil <strong>der</strong> UDF, <strong>der</strong> für die tatsächliche Ausführung des Dijkstra-Algorithmus<br />

zuständig ist, wird also um e<strong>in</strong> vielfaches schneller ausgeführt. Im Rostocker<br />

10 Über alle Nullkanten, die sich am Anfang e<strong>in</strong>er Route zwischen zwei Gateways bef<strong>in</strong>den,<br />

falls vorher schon Nullkanten im Graph vorhanden waren.<br />

11 Die SQL-Anweisung dafür ist im Anhang A.7 aufgeführt.<br />

© Andreas Redmer — 29. September 2011 45


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

Opennet, <strong>in</strong> dem es <strong>der</strong>zeit vier Gateways und etwa 200 Teilnehmer gibt, ist die<br />

Verän<strong>der</strong>ung noch recht kle<strong>in</strong>. Für gleichartige Netzwerke <strong>in</strong> Großstädten, mit<br />

e<strong>in</strong>er höheren Anzahl an Gateways, wäre die Zeitersparnis für die Berechnung<br />

enorm.<br />

4.1.4. Nutzung stabiler Teilergebnisse bei ähnlichen<br />

Graphen<br />

Die General-Gateway-Strategie beschrieb die Optimierung <strong>der</strong> Berechnung auf<br />

e<strong>in</strong>em unverän<strong>der</strong>ten Graph, die ausgeführt wird wenn die Routen für e<strong>in</strong>en<br />

Timestamp berechnet werden. In <strong>der</strong> Praxis kommt es vor, dass die Routen<br />

für mehrere Timestamps berechnet werden sollen. Natürlich könnten alle Routen<br />

vorberechnet und dann <strong>in</strong> <strong>der</strong> Datenbank abgespeichert werden. Damit<br />

wären sie stets sehr schnell erhalten. Wenn man die Zeit für Vorberechnung<br />

ignoriert, erhält man e<strong>in</strong>e sehr schnelle Ermittlung <strong>der</strong> Routen. Diese Art <strong>der</strong><br />

<strong>Performanceoptimierung</strong> soll im Rahmen dieser Arbeit jedoch nicht <strong>durch</strong>geführt<br />

werden, weil man sie ebenso mit allen bestehenden Verfahren hätte<br />

<strong>durch</strong>führen können. Der Zeitaufwand würde dabei reduziert werden, <strong>in</strong>dem<br />

<strong>der</strong> Speicheraufwand stark erhöht wird.<br />

Da die Datenaufzeichnung e<strong>in</strong>mal pro M<strong>in</strong>ute erfolgt, kann man annehmen,<br />

dass aufe<strong>in</strong>an<strong>der</strong> folgende Graphen sehr ähnlich o<strong>der</strong> sogar gleich s<strong>in</strong>d. In diesem<br />

Fall wäre es nur nötig, das Ergebnis e<strong>in</strong>er Berechnung vollständig und<br />

danach jeweils nur den Unterschied zur vorherigen Berechnung zu speichern.<br />

Im folgenden wird erklärt, wie die Ausgabe des Dijkstra-Algorithmus effektiv<br />

gespeichert werden kann.<br />

Die Ausgabe des Dijkstra-Algorithmus umfasst die Menge (D) von Tupeln,<br />

die m<strong>in</strong>destens 12 je e<strong>in</strong>en Knoten (v) und jeweils den Vorgängerknoten (v p ) auf<br />

dem kürzesten Pfad zum nächsten Gateway enthalten.<br />

t = (v, v p )<br />

D = {t|v p ist Vorgänger von v}<br />

Die Menge D hätte immer n Elemente abzüglich <strong>der</strong> Anzahl <strong>der</strong> Gateways,<br />

wobei n die Anzahl <strong>der</strong> Knoten im Graph ist. Sei x die Anzahl <strong>der</strong> <strong>in</strong>sgesamt<br />

12 Das Speichern zusätzlicher Informationen (wie z. B. Weglänge) ist optional.<br />

© Andreas Redmer — 29. September 2011 46


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

aufgezeichneten Timestamps und sei D t das Ergebnis <strong>der</strong> Berechnung zum<br />

Zeitpunkt t, dann s<strong>in</strong>d alle Berechnungsergebnisse mit <strong>der</strong> Menge:<br />

{D 0 , D 1 , D 2 , . . . , D x } ⊆ E<br />

wobei: E = {alle möglichen D}<br />

erfasst. Dabei beschreibt t die Nummer e<strong>in</strong>es Timestamps. Da über den Timestamps<br />

e<strong>in</strong>e strenge Totalordnung def<strong>in</strong>ierbar ist, lassen sie sich h<strong>in</strong>tere<strong>in</strong>an<strong>der</strong><br />

aufzählen.<br />

D a D b M P<br />

Abbildung 4.3.: Venn-Diagramm für zwei Dijkstra-Ergebnismengen<br />

In Abbildung 4.3 ist e<strong>in</strong> Venn-Diagramm zweier Ergebnismengen dargestellt<br />

um die folgenden Schritte anschaulicher zu machen. Um die Ergebnismenge D a<br />

<strong>in</strong> e<strong>in</strong>e an<strong>der</strong>e Ergebnismenge (D b ) umzuwandeln, s<strong>in</strong>d zwei Schritte notwendig.<br />

Es muss e<strong>in</strong>e gewisse Teilmenge (M) von Tupeln entfernt und e<strong>in</strong>e gewisse<br />

weitere Menge (P ) h<strong>in</strong>zugefügt werden.<br />

M = D a \ D b<br />

P = D b \ D a<br />

Wobei <strong>in</strong> jedem Fall M ∈ E und P ∈ E gilt. Der für diese Arbeit sehr wichtige<br />

algebraische Unterschied zwischen zwei Ergebnissen lässt sich dann als<br />

Tupel u speichern<br />

u = (M, P )<br />

und die Menge aller möglichen Unterschiede (U) lässt sich ebenfalls def<strong>in</strong>ieren<br />

U = {alle möglichen u}.<br />

Um den algebraischen Unterschied zwischen zwei Ergebnismengen zu berechnen,<br />

können die Formeln für die Elemente M und P direkt angewendet<br />

werden. Die Speicherung des algebraischen Unterschieds ist somit vollständig<br />

def<strong>in</strong>iert. Für die Speicherung <strong>in</strong> e<strong>in</strong>er relationalen Datenbank kann e<strong>in</strong>e Relati-<br />

© Andreas Redmer — 29. September 2011 47


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

on (Tabelle) angelegt werden, die zu zwei Timestamps jeweils den Unterschied<br />

abspeichert. Diese Relation würde dann Tupel <strong>der</strong> Form<br />

speichern.<br />

v = (t a , t b , u)<br />

Da nicht alle Ergebnismengen e<strong>in</strong>en Vorgänger haben (z. B. ist t 0 <strong>der</strong> erste<br />

je aufgezeichnete Timestamp), sei zusätzlich noch bemerkt, dass sich auch e<strong>in</strong>e<br />

vollständige Ergebnismenge mit e<strong>in</strong>em eben def<strong>in</strong>ierten algebraischen Unterschied<br />

darstellen lässt. Dies geschieht <strong>in</strong>dem die vollständige Menge auf die<br />

leere Menge aufaddiert wird. Also gilt:<br />

M = ∅<br />

P = D 0<br />

und weiterh<strong>in</strong> gilt u = (M, P ). In <strong>der</strong> Datenbank kann die Tatsache, dass<br />

es ke<strong>in</strong>en Vorgänger gibt, gespeichert werden <strong>in</strong>dem <strong>der</strong> NULL-Wert für den<br />

ersten Timestamp abgespeichert wird; so speichert man für t 0 beispielsweise<br />

v = (NULL, t 0 , u).<br />

Weiterh<strong>in</strong> lässt sich, wie folgt, auch e<strong>in</strong>e Funktion diff auf <strong>der</strong> Menge E<br />

def<strong>in</strong>ieren, die <strong>durch</strong> das Aufaddieren e<strong>in</strong>es Unterschieds (M, P ) e<strong>in</strong> neues Element<br />

<strong>in</strong> <strong>der</strong> Menge E erzeugt:<br />

diff : E × U → E, (D a , (M, P )) ↦→ (D a \ M) ∪ P<br />

diff hat folgende Umkehrfunktion:<br />

diff −1 : E × U → E, (D b , (M, P )) ↦→ (D b \ P ) ∪ M<br />

und ist assoziativ. Die algebraische Struktur (E, diff) ist ke<strong>in</strong>e algebraische<br />

Halbgruppe, da diff ke<strong>in</strong>e <strong>in</strong>nere Verknüpfung son<strong>der</strong>n e<strong>in</strong>e Rechtsoperation<br />

von U auf E ist.<br />

Die nun e<strong>in</strong>geführte Algebra wurde im Rahmen dieser Arbeit im PostgreSQL<br />

DBMS implementiert. Die Def<strong>in</strong>ition neuer Datentypen erfolgt <strong>in</strong> PostgreSQL<br />

mit CREATE TYPE aus bereits bestehenden Datentypen, Tupeln von Datentypen<br />

und Arrays von Datentypen. Damit lässt sich jede zuvor angegebene Form<br />

von Mengen o<strong>der</strong> Tupeln implementieren. Mit CREATE FUNCTION lässt sich die<br />

Funktion diff sehr e<strong>in</strong>fach als PL/SQL UDF umsetzen, da die Mengenoperationen<br />

(UNION (∪), INTERSECT (∩) und EXCEPT (\)) bereits im SQL-Server<br />

enthalten s<strong>in</strong>d. Optional kann mit CREATE OPERATOR e<strong>in</strong>e Funktion direkt als<br />

© Andreas Redmer — 29. September 2011 48


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

Operator für e<strong>in</strong>en Datentyp def<strong>in</strong>iert werden, um die Usability <strong>der</strong> Datentypen<br />

zu erhöhen. E<strong>in</strong>e vollständige Implementierung dieser Algebra ist im Anhang<br />

A.8 zu f<strong>in</strong>den.<br />

Zunächst wurde die trivialste Version implementiert, bei <strong>der</strong> nur <strong>der</strong> erste<br />

verfügbare Timestamp (t 0 ) vollständig abgespeichert wurde. Wie man sich<br />

leicht vorstellen kann, dauert die Berechnung relativ lange. Beispielsweise s<strong>in</strong>d<br />

nach nur drei Tagen (4320 M<strong>in</strong>uten) <strong>in</strong> <strong>der</strong> Aufzeichnung schon 4320 neue<br />

Timestamps gespeichert worden. Es muss also 4319 mal die diff Funktion<br />

aufgerufen werden, um die Routen zum Zeitpunkt t 4320 zu erhalten. Dies ist<br />

wesentlich langsamer als die bis dato bereits erreichten Geschw<strong>in</strong>digkeiten <strong>der</strong><br />

Berechnung. Es ist also nötig, mehrere Ergebnisse vollständig abzuspeichern<br />

um e<strong>in</strong>en guten Kompromiss zwischen aufgewandtem Speicher und benötigter<br />

Rechenzeit zur Rekonstruktion e<strong>in</strong>es Ergebnisses zu f<strong>in</strong>den. Beispielsweise<br />

könnte man jedes zehnte o<strong>der</strong> jedes hun<strong>der</strong>tste Ergebnis vollständig speichern<br />

und danach jeweils nur die Unterschiede zu den Nachfolgern. Ebenso ist es<br />

denkbar, dass das Ergebnis des ersten Timestamps e<strong>in</strong>es Tages o<strong>der</strong> e<strong>in</strong>er Stunde<br />

komplett aufgezeichnet wird und alle Nachfolger als Unterschiede.<br />

00:00 Uhr 00:01 Uhr 00:02 Uhr<br />

. . . . . .<br />

00:09 Uhr<br />

00:10 Uhr 00:11 Uhr 00:12 Uhr<br />

. . . . . .<br />

00:19 Uhr<br />

Abbildung 4.4.: Schematische Darstellung - e<strong>in</strong>e vollständige Speicherung alle<br />

10 M<strong>in</strong>uten<br />

E<strong>in</strong>e e<strong>in</strong>fache Analyse <strong>der</strong> Daten hat ergeben, dass sich die Routen sehr stark<br />

än<strong>der</strong>n. Im Durchschnitt verän<strong>der</strong>n sich 10% <strong>der</strong> Routen von e<strong>in</strong>em Timestamp<br />

zum Nächsten. Diese Verän<strong>der</strong>ungsrate war höher als erwartet und es stellte<br />

sich die Frage, ob es damit überhaupt S<strong>in</strong>n macht diese Strategie anzuwenden.<br />

Letztlich bedeutet dies, dass das Ergebnis jedes zehnten Timestamps vollständig<br />

gespeichert werden muss. Abbildung 4.4 stellt dieses Vorgehen schematisch<br />

dar. Dabei kann n(t) = t + 1m<strong>in</strong> als Nachfolgefunktion betrachtet werden,<br />

die beschreibt wie <strong>der</strong> nachfolgende Timestamp zu bestimmen ist. Die Anzahl<br />

<strong>der</strong> mit Unterschieden zu speichernden Timestamps (<strong>in</strong> diesem Fall 10) kann<br />

als Zählwert betrachtet werden.<br />

Wie man sich leicht vorstellen kann, könnte e<strong>in</strong>e verän<strong>der</strong>te Nachfolgefunktion<br />

die Unterschiede deutlich reduzieren. Beispielsweise könnten die Unterschiede<br />

zwischen den geltenden Routen zwischen Montag 00:00 Uhr und Dienstag<br />

00:00 Uhr sehr ger<strong>in</strong>g se<strong>in</strong>. Dies ist <strong>in</strong> Abbildung 4.5 schematisch dargestellt,<br />

© Andreas Redmer — 29. September 2011 49


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

Montag<br />

00:00 Uhr<br />

00:01 Uhr 00:02 Uhr<br />

. . . . . .<br />

23:59 Uhr<br />

Dienstag<br />

00:10 Uhr<br />

00:11 Uhr 00:12 Uhr<br />

. . . . . .<br />

23:59 Uhr<br />

Abbildung 4.5.: Schematische Darstellung - e<strong>in</strong>e vollständige Speicherung pro<br />

M<strong>in</strong>ute im Wochentag<br />

wobei die Nachfolgefunktion dann n(t) = t + 24h wäre und <strong>der</strong> Zählwert vielleicht<br />

wesentlich höher (z. B. 365) gesetzt werden könnte. Es s<strong>in</strong>d noch viele<br />

weitere Möglichkeiten denkbar, wie zum Beispiel, dass <strong>der</strong> Unterschied zwischen<br />

erstem Montag 00:00 Uhr und allen weiteren Montagen 00:00 Uhr gespeichert<br />

wird (also Nachfolgefunktion n(t) = t + 7d und Zählwert z. B. 52).<br />

Im Rahmen dieser Arbeit konnte ke<strong>in</strong>e s<strong>in</strong>nvolle Komb<strong>in</strong>ation von Nachfolgefunktion<br />

und Zählwert gefunden werden, die die Performance erhöht und<br />

vergleichsweise wenig Speicherplatz verbraucht.<br />

Der Vorteil dieses Verfahrens ist also, dass es unter gewissen Umständen und<br />

je nach zur Verfügung stehendem Speicherplatz schneller werden könnte. Die<br />

Nachteile s<strong>in</strong>d jedoch:<br />

1. Es ist zunächst e<strong>in</strong>e <strong>Datenanalyse</strong> nötig, um die ger<strong>in</strong>gsten Unterschiede<br />

zwischen den berechneten Routen zu f<strong>in</strong>den. Dabei ist die effiziente<br />

Durchführung <strong>der</strong> <strong>Datenanalyse</strong> eigentlich e<strong>in</strong>es <strong>der</strong> Ziele (und ke<strong>in</strong>e Voraussetzung)<br />

dieser Arbeit (vgl. Abschnitt 1.4 - Problemklasse B).<br />

2. Es ist e<strong>in</strong>e sehr aufwändige Implementierung erfor<strong>der</strong>lich, da die Daten<br />

nicht vollständig und <strong>durch</strong>aus fehlerhaft s<strong>in</strong>d. So müssen sehr viele Ausnahmen<br />

programmiert werden, die diese Situationen behandeln. Die e<strong>in</strong>fachste<br />

Form <strong>der</strong> Implementierung wäre, als Nachfolgefunktion generell<br />

e<strong>in</strong>fach den nächsten verfügbaren Timestamp zu verwenden, was jedoch<br />

zu <strong>in</strong>effizient ist.<br />

3. Da nur die unmittelbaren Ergebnisse des Dijkstra-Algorithmus gespeichert<br />

werden, s<strong>in</strong>d die Ergebnisse nur für die Problemklasse B verwendbar.<br />

Für die an<strong>der</strong>en <strong>in</strong> Abschnitt 1.4 genannten Problemklassen wäre<br />

diese Optimierung generell nicht verfügbar.<br />

Da die Nachteile stark überwiegen, wurde diese Form <strong>der</strong> Optimierung <strong>in</strong><br />

<strong>der</strong> abschließenden Implementierung nicht e<strong>in</strong>gesetzt.<br />

© Andreas Redmer — 29. September 2011 50


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

4.2. Performanceoptimierter Programmierstil<br />

Unabhängig von <strong>der</strong> praktisch e<strong>in</strong>gesetzten Hardware und von <strong>der</strong> theoretischen<br />

Komplexität e<strong>in</strong>es Algorithmus, lässt sich die Geschw<strong>in</strong>digkeit e<strong>in</strong>es<br />

Programms <strong>durch</strong> den Stil <strong>der</strong> Programmierung erhöhen. In diesem Kapitel,<br />

werden die Techniken erklärt, die <strong>in</strong> den UDF e<strong>in</strong>gesetzt wurden um die Performance<br />

zu erhöhen. Dabei wird <strong>in</strong>sbeson<strong>der</strong>e die Übersetzung des Programms<br />

von Hochsprache <strong>in</strong> Masch<strong>in</strong>encode betrachtet (die <strong>durch</strong> den Compiler <strong>durch</strong>geführt<br />

wird), um e<strong>in</strong>zelne elementare Rechenoperationen auf <strong>der</strong> CPU e<strong>in</strong>zusparen.<br />

4.2.1. Quellcodedesign<br />

Da Java e<strong>in</strong>e objektorientierte Programmiersprache ist, wurden die Java-UDF<br />

im objektorientierten Programmierparadigma erstellt. Die großen Vorteile <strong>der</strong><br />

objektorientierten Programmierung, im Vergleich mit <strong>der</strong> strukturierten Programmierung,<br />

s<strong>in</strong>d die bessere Lesbarkeit, Erweiterbarkeit und Wie<strong>der</strong>verwendbarkeit<br />

des Quelltextes. Grundidee ist dabei, dass alle Variablen Objekte<br />

s<strong>in</strong>d und somit alle Daten <strong>in</strong> Objekte verpackt werden. Beispielsweise wird<br />

aus dem Datentyp <strong>in</strong>t <strong>in</strong> <strong>der</strong> strukturierten Programmiersprache C, die Klasse<br />

Integer <strong>in</strong> <strong>der</strong> objektorientierten Sprache Java. Die Klasse hat lediglich<br />

den Unterschied, dass das eigentliche Datum nicht mehr von außerhalb <strong>der</strong><br />

Klasse gelesen o<strong>der</strong> geschrieben wird, son<strong>der</strong>n <strong>durch</strong> Accessor- und Mutator-<br />

Methoden 13 auf den Ganzzahlwert zugegriffen wird. Man spricht dabei von <strong>der</strong><br />

Datenkapselung. In Java gibt es zur Vere<strong>in</strong>fachung auch weiterh<strong>in</strong> den Datentyp<br />

<strong>in</strong>t. Somit ist Java ke<strong>in</strong>e re<strong>in</strong>e objektorientierte Sprache. Der lesende und<br />

schreibende Zugriff <strong>in</strong> <strong>der</strong> Programmiersprache Assembler (also im Masch<strong>in</strong>encode)<br />

ist bei e<strong>in</strong>er strukturierten Sprache nur e<strong>in</strong> Zugriff auf e<strong>in</strong> Register <strong>in</strong><br />

<strong>der</strong> CPU o<strong>der</strong> e<strong>in</strong> Speicherzugriff. Während <strong>der</strong> Aufruf e<strong>in</strong>er Funktion (wie<br />

Accessor o<strong>der</strong> Mutator) bekanntlich immer mit e<strong>in</strong>em PUSH auf den Stack beg<strong>in</strong>nt,<br />

damit die Register für die lokalen Variablen <strong>in</strong>nerhalb <strong>der</strong> CPU genutzt<br />

werden können, und mit e<strong>in</strong>em POP endet um das Programm nach dem Funktionsaufruf<br />

weiter laufen zu lassen.<br />

Durch mo<strong>der</strong>ne Methoden im Compilerbau werden objektorientierte Programme<br />

nicht automatisch langsamer als strukturierte. E<strong>in</strong> Compiler würde<br />

triviale Accessor- und Mutator-Methoden erkennen und sie ebenso effizient umsetzen.<br />

Dennoch können die Zugriffsmethoden auch deutlich komplexer se<strong>in</strong>.<br />

Bei e<strong>in</strong>em gekapselten Ganzzahlwert könnte beispielsweise auf e<strong>in</strong>en gültigen<br />

13 auch ”<br />

Getter“ und ”<br />

Setter“ genannt<br />

© Andreas Redmer — 29. September 2011 51


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

Wertebereich geprüft werden um ke<strong>in</strong>e ungültigen Daten zu speichern. E<strong>in</strong>e<br />

<strong>der</strong>artige Gültigkeitsprüfung kann e<strong>in</strong>e e<strong>in</strong>fache mathematische Vergleichsoperation<br />

(wie z. B. , =, ≠, . . . ) o<strong>der</strong> aber auch e<strong>in</strong> komplexer Algorithmus<br />

se<strong>in</strong>, <strong>der</strong> ausgeführt wird. Dies ist stark von den Daten abhängig. In jedem<br />

Fall würde <strong>der</strong> Zugriff auf die Daten um m<strong>in</strong>destens e<strong>in</strong>e Operation auf <strong>der</strong><br />

CPU erhöht. Da mo<strong>der</strong>ne CPUs <strong>durch</strong>aus 2 Milliarden FLOPS 14 berechnen<br />

und selbst auch <strong>durch</strong> viele Techniken (wie z. B. Pipel<strong>in</strong><strong>in</strong>g) optimiert s<strong>in</strong>d,<br />

spielt es meist ke<strong>in</strong>e Rolle ob e<strong>in</strong>e o<strong>der</strong> zwei Rechenoperationen ausgeführt<br />

werden. Bei dem vorliegenden Problem, bei dem die Ermittlung aller Routen<br />

zu allen aufgezeichneten Zeitpunkten im Mesh-Netzwerk, mehrere Wochen<br />

dauerte, ist es jedoch <strong>durch</strong>aus s<strong>in</strong>nvoll diese Art <strong>der</strong> <strong>Performanceoptimierung</strong><br />

zu betrachten.<br />

Für diese Art <strong>der</strong> Optimierung ist es nötig, dass man die Optimierungen<br />

die <strong>der</strong> Compiler <strong>durch</strong>führt kennt und möglichst genau absehen kann, wie<br />

das Programm später <strong>in</strong> Masch<strong>in</strong>encode aussehen wird. Idealerweise benutzt<br />

man ke<strong>in</strong>e vorgefertigten abstrakten Datentypen (wie z. B. Stack, Queue o<strong>der</strong><br />

Liste), es sei denn sie s<strong>in</strong>d quelloffen o<strong>der</strong> man kann zum<strong>in</strong>dest im Debugger<br />

ihre genaue Funktionsweise e<strong>in</strong>sehen. Die meisten dieser vorgefertigten Datentypen<br />

s<strong>in</strong>d für den allgeme<strong>in</strong>en Fall optimiert, sie können also für sehr spezielle<br />

Fälle ungeeignet se<strong>in</strong>. Somit ist e<strong>in</strong>e eigene Implementierung empfehlenswert,<br />

wenn dabei <strong>in</strong>tensiv darauf geachtet wird, dass möglichst wenig Operationen<br />

ausgeführt werden. E<strong>in</strong>e gute E<strong>in</strong>führung und e<strong>in</strong>fache Implementierungen von<br />

abstrakten Datentypen f<strong>in</strong>den sich <strong>in</strong> [14].<br />

In <strong>der</strong> zu dieser Arbeit gehörenden Implementation wurde <strong>der</strong> Dijkstra-<br />

Algorithmus auf möglichst primitive Art umgesetzt. Es wurden nur die Datentypen<br />

<strong>in</strong>t und float e<strong>in</strong>gesetzt und wenn nötig Arrays davon gebildet. Die<br />

Klasse Array und die Wrapper-Klassen werden nach Möglichkeit vermieden.<br />

Dies gilt <strong>in</strong>sbeson<strong>der</strong>e für die <strong>in</strong>neren Schleifen, die im Dijkstra-Algorithmus<br />

ablaufen, und teilweise auch für die Vor- und Nachbereitung <strong>der</strong> Daten.<br />

Wie man sich leicht vorstellen kann, wurde da<strong>durch</strong> <strong>der</strong> Entwicklungsaufwand<br />

erhöht und die Lesbarkeit und Wie<strong>der</strong>verwendbarkeit des Quelltextes<br />

reduziert. Tatsächlich ist es immer e<strong>in</strong> Problem, e<strong>in</strong>en guten Kompromiss zwischen<br />

Lesbarkeit und Performance zu f<strong>in</strong>den. Um die Erweiterbarkeit <strong>der</strong> Java-<br />

UDF zu gewährleisten, wurde <strong>der</strong> Quelltext nur an den nötigsten Stellen sehr<br />

stark optimiert. Beim Import <strong>der</strong> Daten aus <strong>der</strong> Datenbank <strong>in</strong> den Algorithmus<br />

und beim Aufbereiten <strong>der</strong> Ausgabe für die Rückgabe an das DBMS, wurden<br />

14 Float<strong>in</strong>g po<strong>in</strong>t operations per second - die Anzahl <strong>der</strong> Fließkomma-Rechenoperationen die<br />

e<strong>in</strong>e CPU pro Sekunde ausführen kann<br />

© Andreas Redmer — 29. September 2011 52


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

wie<strong>der</strong> vermehrt Java-Klassen verwendet, da diese Abschnitte auch vergleichsweise<br />

selten ausgeführt werden.<br />

4.2.2. Zusammenhang zur algorithmischen Komplexität<br />

Die Verwendung des zuvor beschriebenen performanceoptimierten Programmierstils<br />

reduziert die algorithmische Komplexität um e<strong>in</strong>en konstanten Faktor.<br />

Konstante Faktoren werden <strong>in</strong> <strong>der</strong> Komplexitätsbetrachtung meist weggelassen,<br />

da sie die Komplexitätsklasse nicht än<strong>der</strong>n und für große Variablen<br />

(also sehr lange laufende Programme) meist nicht relevant s<strong>in</strong>d.<br />

Wenn man beispielsweise auf e<strong>in</strong>e große Anzahl Ganzzahlwerte mehrfach<br />

lesend zugreifen muss, macht es ke<strong>in</strong>en Unterschied, ob man diese <strong>in</strong> e<strong>in</strong>em<br />

Array o<strong>der</strong> <strong>in</strong> e<strong>in</strong>er Hashmap verwaltet. Beide haben sehr ger<strong>in</strong>ge Zeiten (O(1)<br />

und O(log(n))) für den Zugriff auf e<strong>in</strong> Element 15 . Allerd<strong>in</strong>gs ist e<strong>in</strong>e Array-<br />

Zugriffsfunktion im Masch<strong>in</strong>encode lediglich e<strong>in</strong>e Addition, welche die Speicheradresse<br />

des gesuchten Wertes zurück gibt, während e<strong>in</strong>e Hashfunktion<br />

<strong>durch</strong>aus aus mehreren Additionen besteht. Somit ist <strong>der</strong> konstante Faktor<br />

bei e<strong>in</strong>er Hashfunktion deutlich größer.<br />

Bei <strong>der</strong> Verwendung von vorgefertigten Java-Klassen zur Verwaltung von<br />

Listen, Arrays o<strong>der</strong> Maps, ist darauf zu achten, wie diese Klasse <strong>in</strong>tern funktioniert<br />

und ob sie den gewünschten Ansprüchen entspricht. So ist z. B. die<br />

Java-Klasse Vector e<strong>in</strong>e Mischung aus doppelt verketteter Liste und Array,<br />

die dynamisch je nach Gebrauch um e<strong>in</strong>e variable Anzahl von freien Speicherplätzen<br />

anwachsen kann. Dies ist beim Debugg<strong>in</strong>g <strong>der</strong> Klasse erkennbar. Auch<br />

e<strong>in</strong>e vorgefertigte Array-Klasse kann <strong>in</strong>tern mit doppelt verketteten Listen<br />

o<strong>der</strong> Hashmaps implementiert se<strong>in</strong>. Die meisten Klassen die das Java-Interface<br />

Map o<strong>der</strong> Collection implementieren, lassen sich <strong>durch</strong> e<strong>in</strong>en parametrisierten<br />

Konstruktoraufruf auf e<strong>in</strong>e bestimmte Größe <strong>in</strong>itialisieren, damit sie zur<br />

Laufzeit möglichst nicht vergrößert werden müssen.<br />

Die IP-Adressen die jeden Knoten im Opennet-Netzwerk e<strong>in</strong>deutig identifizieren,<br />

kommen als Zeichenkette im Java Programm an. Dort werden sie mittels<br />

e<strong>in</strong>er Java-Hashmap auf ganze Zahlen abgebildet. Die UDF kann also mit beliebigen<br />

Knotennamen arbeiten, während <strong>der</strong> Dijkstra <strong>in</strong>tern nur mit Ganzzahlen<br />

arbeitet. Derzeit gibt es etwa 200 aktive Knoten im Opennet. Jedoch wurde<br />

die Java-Hashmap mit <strong>der</strong> Größe 500 <strong>in</strong>itialisiert, damit sie auch <strong>in</strong> zukünftigen<br />

Jahren nicht zur Laufzeit wachsen muss. E<strong>in</strong> deutlicher Unterschied zur Implementierung<br />

von Mundt und Vetterick [22] ist, dass die Hashmap nur noch<br />

15 Dies gilt nur, wenn die <strong>in</strong> <strong>der</strong> Hashmap verwendete Hashfunktion effizient berechenbar<br />

ist.<br />

© Andreas Redmer — 29. September 2011 53


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

vor und nach dem Dijkstra-Algorithmus, jedoch nicht mehr während se<strong>in</strong>er<br />

Ausführung verwendet wird.<br />

Generell lässt sich hier auch gut erkennen, dass im Rahmen dieser Arbeit<br />

nicht auf Speicherplatz son<strong>der</strong>n nur auf Zeit optimiert wurde. Bei <strong>der</strong>artig kle<strong>in</strong>en<br />

Graphen (n ≈ 200) braucht auf die Art <strong>der</strong> Verwaltung im Arbeitsspeicher,<br />

bei heutigen Computern, nicht geachtet zu werden.<br />

Wie bereits im Abschnitt 4.1.1 erklärt wurde die Klasse FibonacciHeap aus<br />

dem JGraphT-Framework übernommen. Die Klasse wurde jedoch im Zuge dieser<br />

Optimierung nochmal komplett überarbeitet, da das Framework ebenfalls<br />

für allgeme<strong>in</strong>e Graphen optimiert ist. So konnte je<strong>der</strong> enthaltene Knoten e<strong>in</strong><br />

beliebiges Objekt se<strong>in</strong>, zu dem diverse Zusatz<strong>in</strong>formationen gespeichert wurden.<br />

Dies wurde alles auf den Datentyp <strong>in</strong>t reduziert und alle <strong>in</strong> dieser Klasse<br />

verwendeten vorgefertigten Java-Klassen entfernt. Die neue Klasse wurde<br />

FastFibonacciHeap genannt. Die <strong>durch</strong> diese Optimierung erreichte Zeitersparnis<br />

war sehr ger<strong>in</strong>g. Wie zu erwarten war, brachte diese Optimierung den<br />

kle<strong>in</strong>sten Teil <strong>der</strong> Zeitersparnisse e<strong>in</strong>.<br />

4.2.3. Design Pattern<br />

E<strong>in</strong>e Beson<strong>der</strong>heit bei <strong>der</strong> mo<strong>der</strong>nen Programmierung mit objektorientierten<br />

Programmiersprachen ist die Verwendung von Design Pattern (engl. für Entwurfsmuster).<br />

Softwaretechniker kennen e<strong>in</strong>e ganze Reihe von Design Pattern,<br />

welche die Wie<strong>der</strong>verwendbarkeit und Verän<strong>der</strong>barkeit von Quelltext stark erhöhen.<br />

Auf Design Pattern wurde im Rahmen dieser Arbeit weitestgehend<br />

verzichtet.<br />

Insbeson<strong>der</strong>e solche Pattern wie Factory o<strong>der</strong> Strategy wurden bewusst weggelassen,<br />

da sie zur Laufzeit e<strong>in</strong>e Zeichenkette auslesen, um danach dann e<strong>in</strong>e<br />

bestimmte Klasse zu laden o<strong>der</strong> e<strong>in</strong>en bestimmten Algorithmus auszuführen.<br />

Diese Pattern s<strong>in</strong>d vergleichsweise langsam. Normalerweise ist dieser Geschw<strong>in</strong>digkeitsunterschied<br />

nicht relevant, aber <strong>in</strong> <strong>der</strong> performanceoptimierten Programmierung<br />

sollte nach Möglichkeit darauf verzichtet werden. Factory hätte<br />

z. B. dafür benutzt werden können, die Klasse für den Fibonacci-Heap dynamisch<br />

auszutauschen und mit Strategy hätte man sich zur Laufzeit dynamisch<br />

für verschiedene Graphenalgorithmen entscheiden können.<br />

E<strong>in</strong> Nachteil <strong>der</strong> hier entstandenen Implementierung ist, dass die Metrik<br />

nicht problemlos austauschbar ist. Die Metrik zur Berechnung des Abstands<br />

zwischen zwei Knoten ist fest <strong>in</strong> den Algorithmus e<strong>in</strong>programmiert. E<strong>in</strong> austauschbarer<br />

Komparator o<strong>der</strong> e<strong>in</strong> Strategy-Pattern mit wechselbaren Metriken<br />

hätten hier Abhilfe geschafft wurden jedoch aus Performancegründen nicht e<strong>in</strong>-<br />

© Andreas Redmer — 29. September 2011 54


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

gesetzt. Sollte sich die verwendete Metrik <strong>in</strong> Zukunft mehrmals än<strong>der</strong>n o<strong>der</strong><br />

sogar zwangsweise variabel se<strong>in</strong>, sollte dies korrigiert werden. Der da<strong>durch</strong> zu<br />

erwartende Performanceverlust ist sehr ger<strong>in</strong>g.<br />

4.2.4. Implementierung von ”<br />

unendlich“<br />

In Graphenalgorithmen werden Wege, von denen man noch nicht weiß ob sie<br />

existieren o<strong>der</strong> wie lang sie s<strong>in</strong>d, mit ∞ (unendlich) beschrieben. Dabei kann ∞<br />

im Computer nicht als Wert gespeichert werden. Man ersetzt ∞ also mit e<strong>in</strong>em<br />

zuvor def<strong>in</strong>ierten Wert. Wenn <strong>der</strong> Algorithmus sehr häufig ausgeführt werden<br />

soll, kann die Wahl dieses Wertes die Performance ger<strong>in</strong>gfügig bee<strong>in</strong>flussen. Es<br />

gibt die folgenden drei Möglichkeiten:<br />

1. e<strong>in</strong> dynamisch berechneter hoher Wert (z. B. 2 · ∑m<br />

i=0<br />

w(i); die doppelte<br />

Summe aller Kantengewichte),<br />

2. e<strong>in</strong> Wert außerhalb des Wertebereichs (z. B. NULL, 0, -1) o<strong>der</strong><br />

3. e<strong>in</strong> konstanter sehr großer Wert (z. B. MAX_INT, MAX_FLOAT).<br />

Die erste Möglichkeit ist, dass <strong>der</strong> Wert für jeden speziellen Graphen genau<br />

ermittelt wird und somit theoretisch immer funktioniert. Der Nachteil ist <strong>der</strong><br />

Zeitaufwand für die Berechnung des hohen Wertes. Die zweite Variante setzt<br />

als Maximalwert e<strong>in</strong>en Wert <strong>der</strong> niemals e<strong>in</strong>e Weglänge se<strong>in</strong> kann, da er außerhalb<br />

des Wertebereiches liegt. Wenn es nur positive Kantengewichte im Graph<br />

gibt bietet sich −1 an. In PL/SQL UDF und objektorientierten Programmiersprachen<br />

kann auch <strong>der</strong> Wert NULL verwendet werden. Der Nachteil hierbei ist,<br />

dass die m<strong>in</strong>- und max-Funktionen zum F<strong>in</strong>den des kle<strong>in</strong>eren/größeren von zwei<br />

Werten so angepasst werden müssen, dass diese die vorgenannten Werte immer<br />

als größer betrachten. Diese Variante wurde bei <strong>der</strong> Implementierung des<br />

Floyd-Warshall-Algorithmus <strong>in</strong> e<strong>in</strong>er SQL UDF (vgl. Anhang A.5) verwendet.<br />

Da<strong>durch</strong> wird die m<strong>in</strong>-Funktion jedoch sehr langsam, was die Ausführung des<br />

Algorithmus stark verlangsamt. Die dritte Variante kann e<strong>in</strong>gesetzt werden,<br />

wenn genaue Informationen über den Graphen vorhanden s<strong>in</strong>d, die als fixe<br />

Vorbed<strong>in</strong>gung vorausgesetzt werden können. Für die <strong>in</strong> dieser Arbeit verwendeten<br />

Graphen werden die Kantengewichte als float Werte gespeichert und es<br />

steht fest, dass sie niemals den für float maximal größen Wert (MAX_FLOAT)<br />

erreichen werden. Diese Möglichkeit ist die schnellste <strong>in</strong> <strong>der</strong> Ausführung und<br />

wurde <strong>in</strong> <strong>der</strong> Implementation verwendet.<br />

Die <strong>durch</strong> diese Optimierung erreichte Performanceerhöhung ist relativ zum<br />

betrachteten Vergleichswert. Verglichen mit e<strong>in</strong>em Wert außerhalb des Wertebereiches<br />

und angepasster m<strong>in</strong>-Funktion (also Variante zwei) s<strong>in</strong>d e<strong>in</strong>s und<br />

© Andreas Redmer — 29. September 2011 55


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

drei deutlich schneller. Zwischen Variante e<strong>in</strong>s und drei gibt es nur große<br />

Unterschiede, wenn sehr viele Graphenalgorithmen h<strong>in</strong>tere<strong>in</strong>an<strong>der</strong> ausgeführt<br />

werden sollen.<br />

4.2.5. Adjazenzmatrix statt Adjazenzliste<br />

Die zwei wesentlichen Formen um e<strong>in</strong>en Graphen im Computer zu speichern<br />

s<strong>in</strong>d Adjazenzmatrix und Adjazenzliste. E<strong>in</strong>e Adjazenzmatrix ist e<strong>in</strong> zweidimensionales<br />

Array, welches je e<strong>in</strong>e Spalte und e<strong>in</strong>e Zeile für jeden Knoten hat.<br />

Die e<strong>in</strong>getragenen Werte repräsentieren das Kantengewicht und die Angabe<br />

ob überhaupt e<strong>in</strong>e Kante zwischen den beiden Knoten vorhanden ist. Die Adjazenzliste<br />

ist e<strong>in</strong>e zweidimensionale e<strong>in</strong>fach verkettete Liste (o<strong>der</strong> e<strong>in</strong> Array<br />

von Listen), die für jeden vorhandenen Knoten e<strong>in</strong>e Liste von Nachbarn und<br />

zugehörigem Kantengewicht speichert. Im Allgeme<strong>in</strong>en ist die Adjazenzmatrix<br />

größer und schneller und die Adjazenzliste langsamer und platzsparen<strong>der</strong>. Bei<br />

sehr großen Graphen ist es nicht mehr möglich e<strong>in</strong>e Adjazenzmatrix zu speichern,<br />

weil sie e<strong>in</strong>e quadratische Speicherkomplexität hat (O(n 2 )). Dafür ist<br />

<strong>der</strong> Zugriff auf e<strong>in</strong> spezielles Element bei e<strong>in</strong>er Adjazenzliste langsamer, da<br />

zunächst über die Knoten iteriert werden muss, die zuerst <strong>in</strong> <strong>der</strong> Liste stehen.<br />

Im Rahmen dieser Arbeit wurde nur die Adjazenzmatrix zum Speichern <strong>der</strong><br />

Graphen e<strong>in</strong>gesetzt. Dies geschah <strong>in</strong>sbeson<strong>der</strong>e weil die ersten Implementierungen<br />

noch den e<strong>in</strong>fachen Floyd-Warshall-Algorithmus verwendeten und dieser<br />

den Graphen <strong>in</strong> e<strong>in</strong>er Adjazenzmatrix voraussetzt. E<strong>in</strong> weiterer Grund ist die<br />

e<strong>in</strong>fache Erstellung bzw. <strong>der</strong> e<strong>in</strong>fache Import <strong>der</strong> Daten aus <strong>der</strong> Datenbank<br />

<strong>in</strong> den Algorithmus und die Tatsache, dass <strong>in</strong>tern theoretisch <strong>der</strong> Graphenalgorithmus<br />

dynamisch gewechselt werden kann. Letztlich führte die Tatsache,<br />

dass die Graphen recht kle<strong>in</strong> s<strong>in</strong>d ebenfalls dazu, dass ke<strong>in</strong>e Adjazenzliste e<strong>in</strong>gesetzt<br />

werden musste. Das Speichern von n 2 float-Werten (für n ≈ 200) ist<br />

bei den heutigen Speichergrößen ke<strong>in</strong> Problem.<br />

Der Performanceunterschied, <strong>der</strong> <strong>durch</strong> diese Optimierung erreicht wird ist<br />

relativ hoch, hängt jedoch davon ab wie die Adjazenzliste implementiert ist,<br />

die man zum Vergleich heranzieht. In <strong>der</strong> Implementierung von Mundt und<br />

Vetterick [22] wurden alle Knoten und auch jeweils <strong>der</strong>en Nachbarschaft <strong>in</strong><br />

e<strong>in</strong>er sehr effizienten Baumstruktur (Java-Klasse TreeMap) verwaltet. Jedoch<br />

wurde zur Bestimmung aller Nachbarn zu e<strong>in</strong>em Knoten e<strong>in</strong>e Funktion mit<br />

<strong>der</strong> Komplexität o(m) entwickelt, die die Effizienz e<strong>in</strong>er normalen Adjazenzliste<br />

hat. Sehr langsame und immer wie<strong>der</strong>kehrende Zugriffe auf den Graphen<br />

verlangsamen den Algorithmus stark.<br />

© Andreas Redmer — 29. September 2011 56


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

4.3. Parallelisierung<br />

E<strong>in</strong>e weitere Form <strong>der</strong> Beschleunigung <strong>in</strong> <strong>der</strong> Ausführung des Algorithmus von<br />

Dijkstra ist die Parallelisierung. Dabei gibt es zwei verschiedene Arten:<br />

ˆ die Parallelisierung des Dijkstra-Algorithmus <strong>in</strong> sich, also die parallele<br />

Ausführung auf mehreren Rechnere<strong>in</strong>heiten um die Routen für e<strong>in</strong>en<br />

Graph schneller zu erhalten und<br />

ˆ die Parallelisierung mehrerer Dijkstra-Algorithmen, die die Routen für<br />

verschiedene Graphen parallel berechnen.<br />

Diese beiden Formen wurden im Rahmen dieser Arbeit auf verschiedene<br />

Weisen e<strong>in</strong>gesetzt, die <strong>in</strong> den folgenden beiden Abschnitten erklärt werden.<br />

4.3.1. Multithreaded Dijkstra<br />

Da die Computer, die zum Testen <strong>der</strong> Implementierung zu dieser Arbeit, über<br />

Multicore-Prozessoren verfügten, entstand die Idee, diese zu parallelen Verarbeitung<br />

zu nutzen. Die Anzahl <strong>der</strong> zur Verfügung stehenden CPU-Kerne soll<br />

im folgenden c se<strong>in</strong>. E<strong>in</strong>ige Teile des Dijkstra-Algorithmus lassen sich parallelisieren,<br />

<strong>in</strong>dem an den entsprechenden Stellen c Threads ausgeführt werden.<br />

Java br<strong>in</strong>gt e<strong>in</strong>e API zur e<strong>in</strong>fachen Implementierung von Threads mit sich.<br />

Dabei werden Objekte <strong>der</strong> Klasse Thread erzeugt und mit <strong>der</strong> Methode run()<br />

gestartet.<br />

Im folgenden soll die Parallelisierung des Algorithmus <strong>in</strong> mehrere Threads<br />

erklärt werden. Für die bessere Übersichtlichkeit wird zunächst nochmals <strong>der</strong><br />

Pseudocode für den Dijkstra-Algorithmus aus Kapitel 2.3.1 (List<strong>in</strong>g 2.2) mit<br />

e<strong>in</strong>igen Ergänzungen gegeben.<br />

1 FUNCTION dijkstra() {<br />

2 <strong>in</strong>it()<br />

3 WHILE Q ≠ ∅ {<br />

4 u := m<strong>in</strong>(Q) // Möglichkeit zur Parallelisierung hier<br />

5 Q := Q \ {u}<br />

6 FOR EACH NEIGHBOUR v OF u { // Möglichkeit zur Parallelisierung hier<br />

7 IF v ∈ Q THEN update(u, v)<br />

8 }<br />

9 }<br />

10 }<br />

List<strong>in</strong>g 4.1: Dijkstra-Algorithmus <strong>in</strong> Pseudocode mit Möglichkeiten zur<br />

Parallelisierung<br />

© Andreas Redmer — 29. September 2011 57


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

Im List<strong>in</strong>g 4.1 wurden zwei Kommentare e<strong>in</strong>gefügt, welche die Stellen kennzeichnen,<br />

an denen e<strong>in</strong>e parallele Ausführung möglich ist. In [10] beschreiben<br />

Driscoll et al. e<strong>in</strong>e mögliche E<strong>in</strong>teilung <strong>der</strong> Menge Q (die Menge <strong>der</strong> noch nicht<br />

verarbeiteten Knoten) <strong>in</strong> c Teile. Jedem Prozessor wird dann e<strong>in</strong>er dieser Teile<br />

zur Verarbeitung zugeteilt. Dabei f<strong>in</strong>det je<strong>der</strong> Prozess das für sich lokale M<strong>in</strong>imum.<br />

Mit e<strong>in</strong>er e<strong>in</strong>fachen Methode kann dann mit dem Zeitaufwand log(c)<br />

das globale M<strong>in</strong>imum gefunden werden. Dabei werden alle Prozessoren b<strong>in</strong>ärbaumförmig<br />

angeordnet. Dann gibt je<strong>der</strong> Prozessor se<strong>in</strong> lokales M<strong>in</strong>imum an<br />

se<strong>in</strong>en Eltern-Prozessor weiter. Je<strong>der</strong> Eltern-Prozessor ermittelt wie<strong>der</strong>um das<br />

M<strong>in</strong>imum von den ihm zugereichten Werten se<strong>in</strong>er K<strong>in</strong>d-Prozessoren. Das ganze<br />

wird so lange weitergeführt, bis <strong>der</strong> Prozessor erreicht wird, <strong>der</strong> im Baum<br />

ganz oben steht. Das von ihm ermittelte lokale M<strong>in</strong>imum ist das globale M<strong>in</strong>imum.<br />

Die zweite Möglichkeit zur Parallelisierung ist, die update-Funktion auf<br />

die e<strong>in</strong>zelnen Kerne aufzuteilen. Dabei würde also die Menge <strong>der</strong> Nachbarn des<br />

aktuellen Knotens <strong>in</strong> c Teile zerlegt werden.<br />

Wie jetzt schon leicht zu erkennen ist, ist diese Methode <strong>in</strong>sbeson<strong>der</strong>e für<br />

sehr große Graphen und für e<strong>in</strong>e hohe Anzahl an Prozessoren geeignet. Die<br />

verwendeten Testsysteme verfügen jedoch nur über zwei bzw. vier CPU-Kerne<br />

und die Graphen s<strong>in</strong>d mit n ≈ 200 relativ kle<strong>in</strong>. Diese Methode <strong>der</strong> parallelisierten<br />

Ermittlung des M<strong>in</strong>imums ist für die Implementierung <strong>in</strong> dieser Arbeit<br />

auch nicht relevant, da das M<strong>in</strong>imum immer schon beim E<strong>in</strong>fügen <strong>in</strong> e<strong>in</strong>er<br />

geson<strong>der</strong>ten Variable gespeichert wird und somit immer <strong>in</strong> O(1) Zeit abgefragt<br />

werden kann. Der dafür zusätzliche Aufwand beim E<strong>in</strong>fügen ist ebenfalls<br />

konstant und bei kle<strong>in</strong>en Graphen kaum bemerkbar.<br />

Zum Zeitpunkt als die Parallelisierung <strong>in</strong> die Implementation aufgenommen<br />

wurde, wurden bereits Ausführungszeiten für die gesamte UDF von etwa neun<br />

Millisekunden erreicht, wobei <strong>der</strong> Dijkstra-Algorithmus selbst nur noch etwa<br />

e<strong>in</strong>e Millisekunde Ausführungszeit benötigte. Genaueres dazu wird später im<br />

Abschnitt 4.4 erklärt. Dabei war es nicht mehr möglich diesen extrem kurzen<br />

Zeitraum <strong>durch</strong> Parallelisierung noch zu verkürzen. Das Gegenteil trat e<strong>in</strong>. Die<br />

Ausführungszeiten wurden bis auf 200% erhöht. Dazu muss man sich vorstellen,<br />

dass die relativ kle<strong>in</strong>e Menge aller Nachbarn e<strong>in</strong>es Knotens (z. B. zehn Nachbarknoten)<br />

<strong>in</strong> zwei Teile geteilt wird, dann zwei Thread-Objekte <strong>in</strong>itialisiert<br />

und auf zwei Prozessoren parallel ausgeführt werden. Die Teilung <strong>der</strong> Menge,<br />

die Initialisierung und das Starten <strong>der</strong> Threads dauert lei<strong>der</strong> deutlich länger als<br />

die zehn Update-Operationen seriell auszuführen. Das Problem ist also, dass<br />

die Ausführungsteile die parallelisierbar wären extrem kle<strong>in</strong> s<strong>in</strong>d, und dass<br />

die Aufteilung <strong>der</strong> Menge und das Thread<strong>in</strong>g auch e<strong>in</strong>e gewisse Overhead-Zeit<br />

© Andreas Redmer — 29. September 2011 58


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

benötigen. Diese Form Optimierung wurde wie<strong>der</strong> aus <strong>der</strong> Implementierung<br />

entfernt, da sie ke<strong>in</strong>e Verbesserung brachte.<br />

Der Vollständigkeit halber sei noch erwähnt, dass Brodal et al. <strong>in</strong> [4] e<strong>in</strong>e<br />

Form von parallelen Fibonacci-Heaps vorstellen, die noch schneller ist als die <strong>in</strong><br />

[10] beschriebene. Diese konnte jedoch aus den selben Gründen nicht e<strong>in</strong>gesetzt<br />

werden. Weiterh<strong>in</strong> ist allgeme<strong>in</strong> bekannt, dass beim Dijkstra-Algorithmus auch<br />

die Möglichkeit besteht, ihn vom Startknoten und vom Zielknoten aus gleichzeitig<br />

zu starten. Dafür werden genau zwei CPU-Kerne benötigt. Beide Abläufe<br />

laufen dann aufe<strong>in</strong>an<strong>der</strong> zu und stoppen, im Durchschnitt, nach <strong>der</strong> Hälfte <strong>der</strong><br />

Zeit. Diese Möglichkeit wurde nicht e<strong>in</strong>gesetzt, da im Rahmen dieser Arbeit<br />

ke<strong>in</strong>e Zielknoten def<strong>in</strong>iert werden, son<strong>der</strong>n <strong>der</strong> Algorithmus immer erst stoppt,<br />

wenn alle Knoten entdeckt s<strong>in</strong>d.<br />

Ausführung mehrerer Dijkstras parallel<br />

Obwohl sich <strong>der</strong> Algorithmus <strong>in</strong> sich nicht optimal parallelisieren lässt, gibt<br />

es dennoch Möglichkeiten mehrere Dijkstra-Algorithmen gleichzeitig laufen zu<br />

lassen. Im e<strong>in</strong>fachsten Fall schickt <strong>der</strong> Datenanalyst zwei getrennte Anfragen<br />

an e<strong>in</strong>en Datenbankserver, <strong>der</strong> m<strong>in</strong>destens zwei Prozessoren nutzt. Um beispielsweise<br />

alle Routen zu berechnen, die im November und Dezember des<br />

Jahres 2010 gültig waren, kann er die Funktion shortestPaths, die im Abschnitt<br />

3.2 beschrieben wurde benutzen. Dabei schickt er die beiden <strong>in</strong> List<strong>in</strong>g<br />

4.2 gezeigten Anfragen für die beiden Monate, getrennt über zwei verschiedene<br />

Client-Verb<strong>in</strong>dungen an den Server. Die Ergebnisse könnte er sich beispielsweise<br />

<strong>in</strong> je e<strong>in</strong>e Datei umleiten und danach beide Dateien ane<strong>in</strong>an<strong>der</strong> hängen.<br />

© Andreas Redmer — 29. September 2011 59


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

1 -- Anfrage Nummer 1:<br />

2 SELECT shortestpaths(s1.time) AS paths FROM<br />

3 (<br />

4 SELECT time FROM times WHERE<br />

5 time BETWEEN ’2010-11-01 00:00:00’::timestamptz<br />

6 and ’2010-12-01 00:00:00’::timestamptz<br />

7 ) as s1<br />

8<br />

9 -- Anfrage Nummer 2:<br />

10 SELECT shortestpaths(s1.time) AS paths FROM<br />

11 (<br />

12 SELECT time FROM times WHERE<br />

13 time BETWEEN ’2010-12-01 00:00:00’::timestamptz<br />

14 and ’2011-01-01 00:00:00’::timestamptz<br />

15 ) as s1<br />

List<strong>in</strong>g 4.2: Zwei Anfragen die getrennt gesendet werden können<br />

Diese Art <strong>der</strong> ”<br />

manuellen“ Parallelisierung funktioniert natürlich immer. Das<br />

eben beschriebene Fallbeispiel würde bei e<strong>in</strong>em dedizierten Datenbankserver<br />

das selbe Ergebnis <strong>in</strong> <strong>der</strong> Hälfte <strong>der</strong> Zeit br<strong>in</strong>gen. Das ganze br<strong>in</strong>gt natürlich<br />

nur Vorteile, wenn die Ergebnisse getrennt immer noch verwendbar s<strong>in</strong>d, und<br />

wenn <strong>der</strong>en Ausführung lange genug dauert um e<strong>in</strong>e E<strong>in</strong>teilung zu rechtfertigen.<br />

Um dem Benutzer diese Form <strong>der</strong> Optimierung leichter zugänglich zu machen<br />

wurde im Rahmen dieser Arbeit versucht, e<strong>in</strong>e Schnittstelle zu entwickeln,<br />

die dem Benutzer die Ausführung auf mehreren CPU-Kernen ermöglicht. Dies<br />

sollte wahlweise möglich se<strong>in</strong>, da <strong>der</strong> Benutzer nicht immer sehr große Mengen<br />

abfragt und zusätzlich sollte die <strong>in</strong> Abschnitt 3.2 angegebene Schnittstellendef<strong>in</strong>ition<br />

nicht verän<strong>der</strong>t, son<strong>der</strong>n höchstens erweitert werden.<br />

Den Dijkstra-Algorithmus <strong>in</strong> e<strong>in</strong> Java-Thread-Objekt zu verschieben war<br />

problemlos möglich. Aber dem Benutzer e<strong>in</strong>e effektive Schnittstelle zu bieten<br />

gestaltete sich als schwierig, da <strong>in</strong> SQL e<strong>in</strong>e Möglichkeit gefunden werden musste<br />

um verschiedene Graphen an verschiedene Threads zu übergeben. Generell<br />

hätte man auch mehrere Graphen <strong>in</strong> die Klasse laden können und die Java-<br />

Klasse hätte dann selbst entschieden, wie viele Threads sie dafür eröffnet. Dies<br />

passte jedoch nicht <strong>in</strong> das bestehende Konzept zur Ergebnisrückgabe an die<br />

aufrufende SQL-Funktion.<br />

In Abbildung 3.3 (Seite 33) wurde die Funktion shortestPaths schematisch<br />

dargestellt. Im List<strong>in</strong>g 4.3 f<strong>in</strong>det sich nun die selbe Funktion im Pseudocode.<br />

Daneben wird im List<strong>in</strong>g 4.4 die Funktion shortestPaths, mit dem erweiterten<br />

Multithread-Konzept aufgezeigt.<br />

© Andreas Redmer — 29. September 2011 60


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

1 FUNCTION shortestPath(TIMESTAMP t)<br />

2<br />

3 BEGIN<br />

4 truncateRootNodes()<br />

5 FOR EACH g ∈ Gateways of t<br />

6 DO addRootNode(g) // ≈ 4 mal<br />

7<br />

8<br />

9<br />

10<br />

11<br />

12 truncateL<strong>in</strong>ks()<br />

13 FOR EACH l ∈ L<strong>in</strong>ks of t<br />

14 DO addL<strong>in</strong>k(l) // ≈ 800 mal<br />

15<br />

16<br />

17<br />

18<br />

19<br />

20 run() // läuft ≈ 1 ms<br />

21<br />

22 R := getResultSet()<br />

23<br />

24 RETURN R<br />

25 END<br />

List<strong>in</strong>g 4.3: shortestPaths S<strong>in</strong>glethread<br />

Pseudocode<br />

1 FUNCTION shortestPath(TIMESTAMP t 1 ,<br />

2 TIMESTAMP t 2 )<br />

3 BEGIN<br />

4 truncateRootNodes(CPU1)<br />

5 FOR EACH g ∈ Gateways of t 1<br />

6 DO addRootNode(CPU1,g) //≈4 mal<br />

7<br />

8 truncateRootNodes(CPU2)<br />

9 FOR EACH g ∈ Gateways of t 2<br />

10 DO addRootNode(CPU2,g) //≈4 mal<br />

11<br />

12 truncateL<strong>in</strong>ks(CPU1)<br />

13 FOR EACH l ∈ L<strong>in</strong>ks of t 1<br />

14 DO addL<strong>in</strong>k(CPU1,l) // ≈ 800 mal<br />

15<br />

16 truncateL<strong>in</strong>ks(CPU2)<br />

17 FOR EACH l ∈ L<strong>in</strong>ks of t 2<br />

18 DO addL<strong>in</strong>k(CPU2,l) // ≈ 800 mal<br />

19<br />

20 run(CPU1)<br />

21 run(CPU2)<br />

22 R 1 := getResultSet(CPU1) //≈1 ms<br />

23 R 2 := getResultSet(CPU2)<br />

24 RETURN (R 1 , R 2 )<br />

25 END<br />

List<strong>in</strong>g 4.4: shortestPaths Doublethread<br />

Pseudocode<br />

In dem erweiterten Konzept hat jede <strong>der</strong> <strong>in</strong>neren Funktionen e<strong>in</strong>en neuen<br />

Parameter bekommen, mit dem <strong>der</strong> Benutzer angibt, <strong>in</strong> welchem Thread er<br />

den Graphen berechnen möchte. In diesem Fall wurden die Threads CPU1 und<br />

CPU2 genannt. In <strong>der</strong> Java-Klasse gibt es nun zwei getrennte Repräsentationen<br />

von Graphen, die getrennt mit addRootNode und addL<strong>in</strong>k gefüllt werden. In<br />

Zeile 20 des List<strong>in</strong>gs 4.4 startet die parallele Ausführung. Die Funktion run<br />

hat hier nur noch e<strong>in</strong>e sehr kurze Laufzeit, da sie nur im H<strong>in</strong>tergrund den<br />

Thread startet und dann die Kontrolle wie<strong>der</strong> an die aufrufende Funktion<br />

zurück gibt. Die Laufzeit, die <strong>der</strong> Dijkstra-Algorithmus benötigt, wird nun<br />

von <strong>der</strong> getResultSet-Funktion gebraucht, da diese so lange wartet bis <strong>der</strong><br />

erste Dijkstra-Algorithmus <strong>durch</strong>gelaufen ist.<br />

Die Funktion ist nur beispielhaft für zwei Threads dargestellt. Das verbessert<br />

die Übersichtlichkeit. Man erkennt jedoch leicht, dass man <strong>der</strong> Funktion auch<br />

e<strong>in</strong> Array o<strong>der</strong> e<strong>in</strong>e Liste von x Timestamps hätte übergeben können und diese<br />

dann <strong>in</strong> <strong>der</strong> Java-Klasse auf x Threads aufteilen können. Falls x e<strong>in</strong>e sehr<br />

© Andreas Redmer — 29. September 2011 61


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

große Zahl ist, ist natürlich die Verwendung e<strong>in</strong>er Threadpool-Verwaltung <strong>in</strong><br />

<strong>der</strong> Java-Klasse nicht ausgeschlossen, um die maximale Anzahl <strong>der</strong> erzeugten<br />

Thread-Objekte zu begrenzen.<br />

In Zeile 24 des List<strong>in</strong>gs 4.4 erkennt man jedoch, dass auch hier wie<strong>der</strong> die<br />

Rückgabe des Ergebnisses e<strong>in</strong> Problem ist. In diesem Fall wird e<strong>in</strong> Tupel von<br />

zwei Ergebnismengen zurückgegeben. Da<strong>durch</strong> dass sich <strong>der</strong> Rückgabetyp nun<br />

geän<strong>der</strong>t hat, muss die Funktion <strong>in</strong> e<strong>in</strong>em völlig an<strong>der</strong>en Kontext e<strong>in</strong>gesetzt<br />

werden.<br />

Der Vorteil ist also, dass diese Optimierung die Ausführung von mehreren<br />

Dijkstra-Algorithmen tatsächlich schneller macht. Der Nachteil ist aber, dass<br />

<strong>der</strong> Datenanalyst zwangsweise die Anzahl se<strong>in</strong>er Prozessorkerne kennen muss.<br />

Wenn er beispielsweise vier CPU-Kerne zur Verfügung hat, muss er alle zu<br />

analysierenden Daten immer <strong>in</strong> Vierergruppen zerlegen und absenden. Als Ergebnis<br />

bekommt er wie<strong>der</strong> e<strong>in</strong>e Vierergruppe von Ergebnissen, die er dann<br />

selbst wie<strong>der</strong> entsprechend auswerten muss. Das wäre <strong>in</strong> SQL deutlich komplizierter<br />

als die S<strong>in</strong>gle-Thread-Methode und es wi<strong>der</strong>spricht damit dem Ziel <strong>der</strong><br />

Arbeit e<strong>in</strong>e e<strong>in</strong>fache Lösung bereitzustellen. Durch die Kapselung <strong>der</strong> allgeme<strong>in</strong>en<br />

Java-API-Funktion Runtime.getRuntime().availableProcessors() <strong>in</strong><br />

e<strong>in</strong>e PL/Java UDF, konnte die Anzahl <strong>der</strong> verfügbaren Prozessorkerne schon<br />

<strong>in</strong> SQL bestimmt werden und die shortestPaths-Funktion dann dynamisch<br />

mit <strong>der</strong> entsprechenden Anzahl an Threads aufgerufen werden. Damit wurden<br />

dann aber nur sehr viele geltenden Routen für sehr viele Zeitpunkte h<strong>in</strong>tere<strong>in</strong>an<strong>der</strong><br />

ausgegeben. Dies löst maximal e<strong>in</strong>en Teil <strong>der</strong> Probleme <strong>in</strong> Problemklasse<br />

B und ist somit auch nicht geeignet um das Ziel <strong>der</strong> Arbeit zu erreichen (vgl.<br />

Abschnitt 1.4).<br />

Die Verbesserung <strong>der</strong> Performance <strong>durch</strong> diese Art <strong>der</strong> Parallelisierung war<br />

relativ gut. Allerd<strong>in</strong>gs ist zu bedenken, dass hier nur die Ausführung des<br />

Dijkstra-Algorithmus parallel erfolgt, jedoch nicht die gesamte UDF. Zum Zeitpunkt<br />

<strong>der</strong> E<strong>in</strong>führung dieser Optimierung lief die gesamte UDF bereits <strong>in</strong> nur<br />

noch neun Millisekunden ab, wobei <strong>der</strong> Dijkstra-Algorithmus nur noch e<strong>in</strong>e<br />

Millisekunde davon benötigte. Tabelle 4.1 stellt die obere Schranke <strong>der</strong> da<strong>durch</strong><br />

erreichbaren Verbesserungen da. Dabei wurde angenommen, dass immer<br />

Anzahl Threads = Anzahl CPU-Kerne = Anzahl zu berechnen<strong>der</strong> Graphen<br />

gilt. Der Overhead für das Thread<strong>in</strong>g wurde nicht berücksichtigt.<br />

Dieses Konzept wurde, noch vor <strong>der</strong> vollständigen Implementierung, wie<strong>der</strong><br />

aus dem Projekt entfernt, da die Nachteile (sehr aufwändige Implementierung,<br />

schlechtere Usability und sehr beschränktes E<strong>in</strong>satzgebiet) e<strong>in</strong>em relativ ger<strong>in</strong>-<br />

© Andreas Redmer — 29. September 2011 62


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

Threads Laufzeit S<strong>in</strong>glethread Laufzeit Multithread Verbesserung<br />

1 9 ms 9 ms 0,0%<br />

2 18 ms 17 ms 5,6%<br />

4 36 ms 33 ms 8,3%<br />

8 72 ms 65 ms 9,7%<br />

Tabelle 4.1.: Ausführungszeiten bei gleichzeitiger Ausführung mehrerer<br />

Dijkstra<br />

gen Vorteil (bessere Performance) gegenüberstanden. Insbeson<strong>der</strong>e das breite<br />

E<strong>in</strong>satzgebiet, das <strong>in</strong> Kapitel 5 noch verdeutlicht wird, spricht dafür, dass die<br />

Entscheidung gegen das Multithread<strong>in</strong>g gut war.<br />

4.3.2. Dijkstra auf <strong>der</strong> GPU<br />

Nach <strong>der</strong> Betrachtung Parallelisierung auf Multiprozessorsystemen und die<br />

Parallelverarbeitung <strong>in</strong> <strong>der</strong> Cloud bereits von Mundt und Vetterick <strong>in</strong> [22]<br />

bleibt noch e<strong>in</strong>e weitere Möglichkeit offen, den Graphenalgorithmus zu beschleunigen.<br />

Mo<strong>der</strong>ne Grafikkarten bieten Möglichkeiten, die Berechnungen die<br />

<strong>in</strong> <strong>der</strong> Computergrafik nötig s<strong>in</strong>d stark zu parallelisieren. In früheren Generationen<br />

wurden Berechnungen, die für jeden Pixel e<strong>in</strong>zeln ausgeführt werden<br />

mussten, mit Pixelsha<strong>der</strong>n berechnet. Ebenso wurden Berechnungen für die<br />

Vertices von grafischen 3D Modellen mit Vertexsha<strong>der</strong>n berechnet. Dabei war<br />

<strong>der</strong> Befehlssatz dieser Sha<strong>der</strong> jeweils stark e<strong>in</strong>geschränkt. Da die Ansprüche<br />

immer weiter stiegen, entwickelten die Hersteller <strong>der</strong> Grafikkarten sogenannte<br />

General Purpose Graphics Process<strong>in</strong>g Units (GPGPU); also universelle<br />

Sha<strong>der</strong> die alle Arten von Berechnungen stark parallel ausführen können. Die<br />

Graphics Process<strong>in</strong>g Unit (GPU) dient dabei als Conta<strong>in</strong>er <strong>der</strong> Sha<strong>der</strong> und<br />

wird auch als Grafikprozessor bezeichnet. Die General Purpose Sha<strong>der</strong> können<br />

mit e<strong>in</strong>er API für die Programmiersprache C programmiert werden.<br />

Zunächst entwickelten die großen Hersteller (ATI und Nvidia) eigene Standards.<br />

Dabei war die Compute Unified Device Architecture (CUDA) <strong>der</strong><br />

Standard von Nvidia. Da im Rahmen dieser Arbeit bei e<strong>in</strong>em <strong>der</strong> Testcomputer<br />

e<strong>in</strong>e GPGPU Grafikkarte von Nvidia zur Verfügung stand, wurde versucht<br />

den Dijkstra-Algorithmus ebenfalls mit CUDA umzusetzen. Es sei an dieser<br />

Stelle jedoch bemerkt, dass CUDA nicht <strong>der</strong> aktuellste Standard ist um Berechnungen<br />

auf <strong>der</strong> Grafikkarte <strong>durch</strong>zuführen. Noch mo<strong>der</strong>ner ist die Open<br />

Comput<strong>in</strong>g Language (OpenCL), die ebenfalls von Nvidia mit entwickelt<br />

wurde. OpenCL wurde <strong>in</strong> dieser Arbeit nicht betrachtet, da die Untersuchungen<br />

mit CUDA ausreichend waren.<br />

© Andreas Redmer — 29. September 2011 63


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

Die Programmierung von GPUs ist e<strong>in</strong> großes eigenes Themengebiet, das an<br />

dieser Stelle nicht ausführlich erläutert werden soll. Wichtig ist jedoch zu wissen,<br />

dass die Grafikkarte e<strong>in</strong>e komplett eigene Architektur (also e<strong>in</strong> Computer<br />

im Computer) ist. Auf ihr gibt es Sha<strong>der</strong>, die jeweils alle ihren eigenen kle<strong>in</strong>en<br />

Arbeitsspeicher haben und zusätzlich gibt es e<strong>in</strong>en größten Arbeitsspeicher auf<br />

den alle Sha<strong>der</strong> geme<strong>in</strong>sam zugreifen können.<br />

Harish und Narayanan stellen <strong>in</strong> [17] Möglichkeiten vor, e<strong>in</strong>e Vielzahl von<br />

Graphenalgorithmen mit CUDA auf <strong>der</strong> Grafikkarte zu implementieren. Sie<br />

implementierten auch Floyd-Warshall und Dijkstra und stellten ihre Implementierung<br />

für die Erstellung dieser Arbeit freundlich zur Verfügung. Diese<br />

Implementierung bestand aus e<strong>in</strong>em C-Programm und zwei CUDA-Dateien.<br />

Die CUDA-Dateien enthalten den m<strong>in</strong>imal kle<strong>in</strong>en Sourcecode, <strong>der</strong> mit dem<br />

Nvidia Compiler (NVC) zu e<strong>in</strong>er CUBIN-Datei (CUDA B<strong>in</strong>ary) kompiliert<br />

werden kann. Diese enthält den b<strong>in</strong>ären Masch<strong>in</strong>encode <strong>der</strong> auf GPGPUs ausgeführt<br />

werden kann. Das C-Programm folgte größtenteils e<strong>in</strong>em üblichen Schema,<br />

nach dem auch sehr viele von Nvidia veröffentlichte Beispielprogramme<br />

entworfen s<strong>in</strong>d. Dieses Schema ist <strong>in</strong> List<strong>in</strong>g 4.5 aufgezeigt.<br />

1 LESE Graph aus Datei<br />

2 ALLOKIERE den Host-Speicher (auf dem Computer)<br />

3 INITIALISIERE den Host-Speicher<br />

4 ALLOKIERE den Gerätespeicher (auf dem Grafikkarte)<br />

5 KOPIERE den Inhalt von Host-Speicher zu Gerätespeicher<br />

6 ALLOKIERE den Host-Speicher für das Ergebnis<br />

7 ALLOKIERE den Gerätespeicher für das Ergebnis<br />

8 STARTE den Algorithmus (nutzt die CUBIN-Programme)<br />

9 KOPIERE das Ergebnis von Gerätespeicher zu Host-Speicher<br />

10 SPEICHERE das Ergebnis <strong>in</strong> e<strong>in</strong>er Datei<br />

11 GIB Host-Speicher FREI<br />

12 GIB Gerätespeicher FREI<br />

List<strong>in</strong>g 4.5: Ablauf des CUDA-Programmes <strong>in</strong> Pseudocode<br />

Dieses C-Programm wurde im Rahmen dieser Arbeit nach Java portiert.<br />

Dabei wurde das JCuda Framework [19] verwendet. Anschließend konnte die<br />

CUDA-Implementierung mit den selben Graphen und mit den selben Testklassen<br />

getestet werden. Dabei wurde festgestellt, dass diese langsamer s<strong>in</strong>d,<br />

was ganz offensichtlich mit <strong>der</strong> aufwändigen Vor- und Nachbereitung des Programms<br />

zusammen h<strong>in</strong>g. Die Kopiervorgänge <strong>in</strong> List<strong>in</strong>g 4.5 (Zeile 5 und 9) vom<br />

Arbeitsspeicher des Computers <strong>in</strong> den <strong>der</strong> Grafikkarte und zurück, dauern lei<strong>der</strong><br />

viel zu lange um für <strong>der</strong>artig kle<strong>in</strong>e Graphen e<strong>in</strong>en Geschw<strong>in</strong>digkeitsvorteil<br />

zu erreichen.<br />

© Andreas Redmer — 29. September 2011 64


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

Harish und Narayanan geben <strong>in</strong> [17] an, dass ihre GPU-Implementierung für<br />

zufällige Graphen deutlich schneller ist, als e<strong>in</strong>e vergleichbare CPU-Version.<br />

Allerd<strong>in</strong>gs hängt dies stark von <strong>der</strong> Anzahl <strong>der</strong> Nachbarknoten pro Knoten ab.<br />

Für Graphen mit e<strong>in</strong>er Millionen Knoten mit 6 Nachbarn pro Knoten (was auch<br />

im Opennet <strong>durch</strong>schnittlich so ist) erreichten sie e<strong>in</strong>e Ausführungszeit von<br />

100 Millisekunden, während die CPU schon 10 Sekunden dafür benötigte. Die<br />

Zeitmessung erfolgt dabei allerd<strong>in</strong>gs nur über den re<strong>in</strong>en Dijkstra-Algorithmus.<br />

Die Zeiten für den Transfer <strong>der</strong> Daten zwischen den Arbeitsspeichern wurden<br />

ignoriert. Für kle<strong>in</strong>ere Graphen wurden ke<strong>in</strong>e Testwerte angegeben.<br />

Für die Relevanz <strong>in</strong> dieser Arbeit mussten aber auch die Zeiten für die Datentransfers<br />

mitgemessen werden. Die Ausführungen mit Graphen mit 200 Knoten<br />

dauerten über 15 Millisekunden (das wären dann 15+8=23 Millisekunden für<br />

die gesamte UDF). Da die UDF damit nicht beschleunigt werden konnten, wurde<br />

diese Optimierung nicht <strong>in</strong> die Implementierung <strong>der</strong> UDF aufgenommen.<br />

4.4. Zusammenfassung und Ergebnisse<br />

Die f<strong>in</strong>ale Form <strong>der</strong> UDF, die aus genannten Gründen ohne jegliche Parallelisierung<br />

funktioniert, hat die <strong>in</strong> Abbildung 4.6 aufgezeigten Laufzeiten. Diese<br />

wurde mit den <strong>in</strong> Abschnitt 3.3 vorgestellten Methoden so genau wie möglich<br />

gemessen. Die Java-UDF läuft 9,08 Millisekunden. Dabei handelt es sich um<br />

die <strong>durch</strong>schnittliche Laufzeit für e<strong>in</strong>e UDF, die über allen zur Verfügung stehenden<br />

Daten ermittelt wurde.<br />

3,23<br />

9,08<br />

1,805<br />

1,04<br />

3,905<br />

shortestPaths (100 %)<br />

truncate/addRootNodes<br />

(0,14 ms; 1,54 %)<br />

truncate/addL<strong>in</strong>ks (35,57 %)<br />

run (19,88 %)<br />

dijkstra (11,45 %)<br />

getResultSet (43,01 %)<br />

0 1 2 3 4 5 6 7 8 9 10 ms<br />

Abbildung 4.6.: Balkendiagramm <strong>der</strong> Ausführungszeit <strong>der</strong> UDF shortest-<br />

Paths<br />

Die Ausführung des Dijkstra-Algorithmus dauert <strong>in</strong> <strong>der</strong> f<strong>in</strong>alen Version nur<br />

noch 1,04 Millisekunden. Es ist sehr wichtig zu beachten, dass alle Optimierun-<br />

© Andreas Redmer — 29. September 2011 65


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

gen, die den Dijkstra-Algorithmus betrafen (z. B. General-Gateway-Strategie,<br />

Dijkstra auf <strong>der</strong> GPU, etc.), sich immer nur auf diesen Zeitraum auswirkten.<br />

Insbeson<strong>der</strong>e können auch zukünftige Optimierungen am Dijkstra-Algorithmus<br />

nur e<strong>in</strong>e Verr<strong>in</strong>gerung dieser 1,04 Millisekunden bewirken. Damit kann auch<br />

die zu erwartende Zeitersparnis für zukünftige Optimierungen gut e<strong>in</strong>geschätzt<br />

werden.<br />

In Abbildung 4.7 wird die Reihenfolge <strong>der</strong> <strong>durch</strong>geführten Optimierungen<br />

dargestellt und wie diese aufe<strong>in</strong>an<strong>der</strong> aufbauen. Die Tabelle 4.2 zeigt e<strong>in</strong>e Zusammenfassung<br />

dieser Optimierungen. Um die Ergebnisse nachvollziehbar zu<br />

machen, wurde <strong>der</strong> Abschnitt, <strong>in</strong> dem diese Art <strong>der</strong> Optimierung beschrieben<br />

wurde, <strong>in</strong> je<strong>der</strong> Zeile genannt.<br />

1<br />

Implementierung<br />

aus [22]<br />

2<br />

Floyd-Warshall<br />

<strong>in</strong> SQL<br />

10<br />

ohne Fibonacci-<br />

Heap mit<br />

Multithreaded<br />

Dijkstra<br />

3<br />

Floyd-Warshall<br />

<strong>in</strong> PL/Python<br />

6<br />

mit JGraphT<br />

Fibonacci-Heap<br />

11<br />

mit mehreren<br />

Dijkstras<br />

parallel<br />

4 5<br />

Floyd-Warshall<br />

<strong>in</strong> PL/Java<br />

jetzt mit<br />

Dijkstra<br />

7 8 9<br />

mit eigenem<br />

FastFibonacci-<br />

Heap<br />

mit General-<br />

Gateway-<br />

Strategie<br />

mit gestrichenen<br />

redundanten<br />

Kanten<br />

12<br />

ohne<br />

Fibonacci-Heap<br />

auf <strong>der</strong> GPU<br />

Abbildung 4.7.: Reihenfolge <strong>der</strong> <strong>durch</strong>geführten Optimierungen<br />

In Tabelle 4.2 wurde die Implementierung von Mundt und Vetterick [22] als<br />

Referenz angegeben um die erreichte Verbesserung prozentual ausdrücken zu<br />

können. E<strong>in</strong>e relative Verbesserung konnte nicht angegeben werden, da diese<br />

immer vom betrachteten Vorgänger abhängig wäre. Beispielsweise br<strong>in</strong>gt<br />

die General-Gateway-Strategie prozentual unterschiedliche Verbesserungen, je<br />

nachdem welche Programmiersprache und welche Art von Heap e<strong>in</strong>gesetzt<br />

wird. Theoretisch sollte es <strong>der</strong>artige Unterschiede nicht geben; <strong>in</strong> <strong>der</strong> Praxis<br />

treten sie jedoch aufgrund verschiedenartiger Compileroptimierungen auf.<br />

© Andreas Redmer — 29. September 2011 66


4. Optimierungen <strong>in</strong> <strong>der</strong> Implementierung<br />

Nr. Optimierung Abschnitt Laufzeit <strong>in</strong> ms Laufzeit <strong>in</strong> %<br />

1 Implementierung aus [22] 2.3.1 ∼ = 4000 ms 100%<br />

2 Floyd-Warshall <strong>in</strong> SQL 3.1.3 > 60000 ms 1500%<br />

3 Floyd-Warshall <strong>in</strong> 3.1.3 = 6000 ms 150%<br />

PL/Python<br />

4 Floyd-Warshall <strong>in</strong> 3.1.3 = 250 ms 6,25%<br />

PL/Java<br />

5 jetzt mit Dijkstra 4.1.1 = 20,02 ms 0,50050%<br />

6 mit JGraphT<br />

4.1.1 = 12,26 ms 0,30650%<br />

Fibonacci-Heap<br />

7 mit eigenem<br />

4.2.2 = 12,23 ms 0,30575%<br />

FastFibonacciHeap<br />

8 mit General-Gateway- 4.1.3 = 9,15 ms 0,22875%<br />

Strategie<br />

9 mit gestrichenen 4.1.2 = 9,08 ms 0,22700%<br />

redundanten Kanten<br />

10 ohne Fibonacci-Heap mit 4.3.1 ∼ = 12,2 ms 0,30500%<br />

Multithreaded Dijkstra<br />

11 mit mehreren Dijkstras 4.3.1 ∼ = 8,5 ms 0,21250%<br />

parallel<br />

12 ohne Fibonacci-Heap auf 4.3.2 ∼ = 23 ms 0,57500%<br />

<strong>der</strong> GPU<br />

Tabelle 4.2.: Ausführungszeiten und Verbesserungen <strong>der</strong> Optimierungen<br />

Zusätzlich gelten für die Tabelle 4.2 folgende H<strong>in</strong>weise:<br />

ˆ Nr. 9 representiert die f<strong>in</strong>ale Version dieser Arbeit,<br />

ˆ gemessene Werte werden mit ”<br />

=“ angegeben,<br />

ˆ geschätzte o<strong>der</strong> berechnete Werte werden mit ”<br />

∼ =“ angegeben,<br />

ˆ für Nr. 10 gilt 2 Threads und 2 verfügbare CPU-Kerne,<br />

ˆ für Nr. 11 gilt 2 Threads, 2 verfügbare CPU-Kerne und 2 zu berechnende<br />

Graphen (Durschnittswert) und<br />

ˆ für alle Messungen wurde das Testsystem 1 verwendet.<br />

Alle im Abschnitt 4.2 (performanceoptimierter Programmierstil) genannten<br />

Optimierungen wurden je<strong>der</strong>zeit angewendet. Auch wenn die da<strong>durch</strong> erhaltenen<br />

Verbesserungen nicht gemessen wurden, erkennt man schon an dem<br />

Sprung von Nr. 6 auf Nr. 7, dass die Unterschiede da<strong>durch</strong> wahrsche<strong>in</strong>lich nur<br />

sehr ger<strong>in</strong>g waren. Die <strong>in</strong> Abschnitt 2.3.1 schon beschriebene, sehr effiziente,<br />

Implementierung des Dijkstra-Algorithmus, wurde ebenfalls von Anfang an<br />

verwendet. Sie ist also schon <strong>in</strong> Nr. 5 enthalten und bee<strong>in</strong>flusst die Laufzeit<br />

relativ stark.<br />

© Andreas Redmer — 29. September 2011 67


5. Testläufe - Beispiele für Datenabfragen<br />

5. Testläufe - Beispiele für<br />

Datenabfragen<br />

Dieses Kapitel zeigt e<strong>in</strong>e Reihe von Abfragen, <strong>in</strong> denen die im Rahmen dieser<br />

Arbeit erstellten UDF e<strong>in</strong>gesetzt werden. In den hier gezeigten Beispielen kann<br />

e<strong>in</strong> Datenanalyst erkennen, die wie die Funktionen zu verwenden s<strong>in</strong>d und<br />

welche Möglichkeiten sich damit bieten.<br />

Die Beispiele setzen voraus, dass die Daten <strong>in</strong> <strong>der</strong> <strong>in</strong> Tabelle 1.1 (Seite 3)<br />

gezeigten Form <strong>in</strong> e<strong>in</strong>er Tabelle namens l<strong>in</strong>ks auf dem Datenbankserver abgelegt<br />

s<strong>in</strong>d. Da die Angabe <strong>der</strong> Zeitpunkte (Timestamps) aus den Aufzeichnungen<br />

immer benötigt wird und die Abfrage aller vorhandenen Zeitpunkte (ohne<br />

Duplikate) sehr lange dauert, wurden diese im Vorfeld <strong>in</strong> e<strong>in</strong>e separate Tabelle<br />

geschrieben. Dazu wurde folgende Anweisung verwendet:<br />

CREATE TABLE times AS SELECT DISTINCT time FROM l<strong>in</strong>ks;<br />

Dabei werden nur die Timestamps ausgewählt, alle Duplikate entfernt und<br />

dann <strong>in</strong> die Tabelle times geschrieben. Die Datenbank für die nachfolgenden<br />

SQL-Abfragen kann nun mit dem <strong>in</strong> Abbildung 5.1 gezeigten Entity-<br />

Relationship-Modell dargestellt werden.<br />

nodeA<br />

(1,1) (1,N)<br />

Time Has L<strong>in</strong>k<br />

nodeB<br />

LQ<br />

time<br />

time<br />

NLQ<br />

Abbildung 5.1.: ER-Modell <strong>der</strong> verwendeten Datenbank<br />

Die Spalte time wurde <strong>in</strong> je<strong>der</strong> <strong>der</strong> beiden Tabellen mit e<strong>in</strong>em B-Baum <strong>in</strong>diziert.<br />

Da<strong>durch</strong> s<strong>in</strong>d partial-match-Anfragen und exact-match-Anfragen sehr<br />

schnell möglich. Ohne diese währen die angegebenen Laufzeiten teilweise nicht<br />

© Andreas Redmer — 29. September 2011 68


5. Testläufe - Beispiele für Datenabfragen<br />

erreichbar. Dabei ist zu beachten, dass Anfragen mit e<strong>in</strong>er WHERE-Klausel <strong>der</strong><br />

Form:<br />

WHERE time BETWEEN ’2011-01-01 00:00:00’ AND ’2011-12-31 23:59:59’<br />

e<strong>in</strong>e ger<strong>in</strong>gere Ausführungszeit als Anfragen mit <strong>der</strong> Form:<br />

WHERE EXTRACT (year from time) = 2011<br />

haben. Beide liefern die selbe Ergebnismenge. Der Index wird jedoch nur bei<br />

Verwendung e<strong>in</strong>es Vergleichsoperators verwendet, nicht jedoch bei <strong>der</strong> Ausführung<br />

e<strong>in</strong>er Funktion. Zusätzlich wäre es möglich den Rückgabewert e<strong>in</strong>er<br />

Funktion zu <strong>in</strong>dizieren, was für die im Anschluß gezeigten SQL-Anfragen jedoch<br />

nicht nötig war.<br />

Soweit nicht an<strong>der</strong>s angegeben, wurden alle Laufzeiten auf dem Testsystem 1<br />

gemessen, das <strong>in</strong> Tabelle 1.6 (Seite 11) def<strong>in</strong>iert wurde.<br />

Die gegebenen SQL-Abfragen lesen sich am besten ”<br />

von <strong>in</strong>nen nach außen“.<br />

Also <strong>in</strong> <strong>der</strong> Reihenfolge <strong>in</strong> <strong>der</strong> sie auf dem Server ausgeführt werden. Innere<br />

SQL-Selektionen wurden weiter e<strong>in</strong>gerückt und teilweise <strong>der</strong> Reihenfolge nach<br />

benannt (z. B. mit s1, s2, s3, s4). Für jedes Beispiel wird die Ausgabe, die<br />

Laufzeit und e<strong>in</strong>e ausführliche Erklärung angegeben. Weiterh<strong>in</strong> gibt es zu e<strong>in</strong>igen<br />

<strong>der</strong> Beispiele e<strong>in</strong> Fazit, welche e<strong>in</strong>e Datenanalyst beispielhaft aus den<br />

Ergebnissen ziehen könnte. Dabei handelt es sich um tatsächliche Schlussfolgerungen,<br />

die das real bestehende Netzwerk betreffen.<br />

© Andreas Redmer — 29. September 2011 69


5. Testläufe - Beispiele für Datenabfragen<br />

5.1. Alle Routen zu allen Zeitpunkten<br />

1 SELECT s1.time, (shortestpaths(s1.time)).* as PE from<br />

2 (<br />

3 select time from times<br />

4 ) as s1;<br />

List<strong>in</strong>g 5.1: Abfrage aller Routen<br />

Ausgabe: 57396862 Zeilen von Tupeln <strong>der</strong> Form: (Timestamp, Knoten, Vorgänger,<br />

Abstand)<br />

Laufzeit: ≈ 74 M<strong>in</strong>uten (Testsystem 1), ≈ 83 M<strong>in</strong>uten (Testsystem 2)<br />

Erklärung: Die Anfrage <strong>in</strong> List<strong>in</strong>g 5.1 ist die Standardabfrage, die den<br />

meisten Performancetests als Benchmark diente. Sie gibt alle geltenden Routen<br />

zu allen aufgezeichneten Zeitpunkten zurück. Damit können Fragen aus <strong>der</strong><br />

Problemklasse B (vgl. Abschnitt 1.4) beantwortet werden. Die Ausführungszeit<br />

für alle shortestPaths-Funktionen ergibt sich aus <strong>der</strong> Anzahl <strong>der</strong> bestehenden<br />

Timestamps (334960) mal <strong>der</strong> Zeit die e<strong>in</strong>e UDF-Berechnung benötigt (9 ms).<br />

334960 · 9ms = 3014640ms ≈ 50m<strong>in</strong><br />

H<strong>in</strong>zu kommt <strong>in</strong> diesem Fall die Ausgabe <strong>der</strong> enormen Anzahl <strong>der</strong> Ergebniszeilen,<br />

die erhebliche Zeit <strong>in</strong> Anspruch nimmt. In diesem Fall erfolgte die Ausgabe<br />

<strong>in</strong> e<strong>in</strong>e Datei, die das Ergebnis <strong>in</strong> Textform repräsentiert. Die Datei war 3,0<br />

Gigabytes groß. Die höhere Laufzeit für Testsystem 2 resultiert aus <strong>der</strong> langsameren<br />

Festplatte.<br />

© Andreas Redmer — 29. September 2011 70


5. Testläufe - Beispiele für Datenabfragen<br />

5.2. Routenän<strong>der</strong>ungen zwischen zwei<br />

Zeitpunkten<br />

1 DROP FUNCTION IF EXISTS unterschied(timestamptz,timestamptz);<br />

2 CREATE OR REPLACE FUNCTION unterschied(timestamptz,timestamptz)<br />

3 RETURNS big<strong>in</strong>t AS<br />

4 $$<br />

5 select count (*) from<br />

6 (<br />

7 (<br />

8 select node,pred from shortestpaths($1)<br />

9 EXCEPT<br />

10 select node,pred from shortestpaths($2)<br />

11 )<br />

12 UNION<br />

13 (<br />

14 select node,pred from shortestpaths($2)<br />

15 EXCEPT<br />

16 select node,pred from shortestpaths($1)<br />

17 )<br />

18 ) s1<br />

19 $$<br />

20 STABLE LANGUAGE SQL;<br />

21<br />

22 SELECT t1.time,t2.time,unterschied(t1.time,t2.time) FROM times t1,times t2<br />

23 WHERE t1.time BETWEEN ’2010-09-14 00:00:00’<br />

24 AND ’2010-09-15 00:00:00’<br />

25 AND t2.time = (SELECT MIN(time) FROM times t3 WHERE t3.time >t1.time)<br />

List<strong>in</strong>g 5.2: Abfrage aller Routenän<strong>der</strong>ungen an e<strong>in</strong>em Tag<br />

Ausgabe: 1440 Zeilen <strong>der</strong> Form (Timestamp 1,Timestamp 2, Anzahl <strong>der</strong><br />

Unterschiede)<br />

Laufzeit: 53 Sekunden<br />

Erklärung: Die Anfrage <strong>in</strong> List<strong>in</strong>g 5.2 löst e<strong>in</strong> Problem aus <strong>der</strong> Problemklasse<br />

C. Sie gibt aus, wie stark sich die Routen von e<strong>in</strong>em Zeitpunkt zum<br />

nächsten an e<strong>in</strong>em Tag än<strong>der</strong>n. Der E<strong>in</strong>fachheit wegen, wird dabei zunächst<br />

e<strong>in</strong>e e<strong>in</strong>fache neue UDF namens unterschied angelegt. Diese bekommt zwei<br />

Timestamps als Parameter und gibt die Anzahl <strong>der</strong> Unterschiede zwischen den<br />

Routen <strong>der</strong> beiden Timestamps zurück. Der Unterschied ist dabei wie folgt<br />

def<strong>in</strong>iert: sei D x die Menge aller Knoten mit zugehörigen Vorgängern zum<br />

© Andreas Redmer — 29. September 2011 71


5. Testläufe - Beispiele für Datenabfragen<br />

Zeitpunkt x. Sei weiterh<strong>in</strong> <strong>der</strong> Vorgänger immer <strong>der</strong> Knoten, <strong>der</strong> auf dem Weg<br />

zum nächsten Gateway direkt angesprochen wird. Dann ist<br />

|(D i \ D j ) ∪ (D j \ D i )|<br />

e<strong>in</strong> Maß für den Unterschied zwischen Zeitpunkt i und j.<br />

Die SQL-Anfrage die <strong>in</strong> Zeile 22 beg<strong>in</strong>nt, listet alle Timestamps, die am<br />

14.09.2010 aufgezeichnet wurden auf und ordnet ihnen ihren Nachfolger zu.<br />

Als Nachfolger wird immer <strong>der</strong> nächst größere aufgezeichnete Timestamp ausgewählt.<br />

Dann wird die Funktion unterschied auf jedes Paar angewendet.<br />

Die Ausführungszeit für e<strong>in</strong>e solche Anfrage kann wie folgt im Vorfeld geschätzt<br />

werden, da die Anzahl <strong>der</strong> M<strong>in</strong>uten pro Tag (1440) bekannt ist.<br />

Timestamps · shortestpaths() <strong>in</strong> unterschied() · Dauer shortestpaths()<br />

} {{ } } {{ } } {{ }<br />

1440<br />

4<br />

9ms<br />

1440 · 4 · 9ms = 51840ms ≈ 52s<br />

Der maximale Unterschied zwischen zwei Timestamps kann abgefragt werden,<br />

<strong>in</strong>dem die Aggregatfunktion MAX im SELECT-Teil <strong>der</strong> Anweisung verwendet<br />

wird. Ebenso s<strong>in</strong>d weitere Funktionen wie MIN o<strong>der</strong> AVG möglich, um die 1440<br />

Zeilen zusammenfassend zu betrachten. Wie man leicht sieht, lassen sich diese<br />

Ergebnisse dann wie<strong>der</strong>um <strong>in</strong> UDF kapseln und/o<strong>der</strong> <strong>in</strong> weiteren komplexeren<br />

Anfragen verwenden.<br />

Fazit: Am 14.09.2010 lag <strong>der</strong> maximale Unterschied, zwischen zwei aufe<strong>in</strong>an<strong>der</strong><br />

folgenden Timestamps, bei 68 und <strong>der</strong> m<strong>in</strong>imale Unterschied bei 6<br />

Verän<strong>der</strong>ungen. Durchschnittlich gab es 26,2 Verän<strong>der</strong>ungen zwischen zwei Timestamps.<br />

© Andreas Redmer — 29. September 2011 72


5. Testläufe - Beispiele für Datenabfragen<br />

5.3. Routenän<strong>der</strong>ungen bei Ausfall e<strong>in</strong>es Knotens<br />

1 CREATE OR REPLACE FUNCTION shortestpaths_skip1(timestamptz, <strong>in</strong>et)<br />

2 RETURNS SETOF PathElement AS $$ DECLARE<br />

3 BEGIN<br />

4 PERFORM truncateRootNodes();<br />

5 PERFORM addRootNode (host(’192.168.0.254’::<strong>in</strong>et));<br />

6 PERFORM addRootNode (host(’10.2.0.251’::<strong>in</strong>et));<br />

7 PERFORM addRootNode (host(’10.2.0.247’::<strong>in</strong>et));<br />

8 PERFORM addRootNode (host(’192.168.0.251’::<strong>in</strong>et));<br />

9 PERFORM truncateL<strong>in</strong>ks();<br />

10 PERFORM addL<strong>in</strong>k(host(nodea), host(nodeb), lq, lqn) from l<strong>in</strong>ks<br />

11 where time=$1<br />

12 AND nodea!=$2 AND nodeb!=$2; -- NEU<br />

13<br />

14 PERFORM run();<br />

15 RETURN QUERY SELECT node,pred,d from getPathSet();<br />

16 END;<br />

17 $$ STABLE LANGUAGE plpgsql;<br />

18<br />

19 CREATE OR REPLACE FUNCTION unterschied_skip1(timestamptz,<strong>in</strong>et)<br />

20 RETURNS big<strong>in</strong>t AS $$<br />

21 select count (*) from<br />

22 ( ( select node,pred from shortestpaths($1) EXCEPT<br />

23 select node,pred from shortestpaths_skip1($1,$2)<br />

24 ) UNION<br />

25 ( select node,pred from shortestpaths_skip1($1,$2) EXCEPT<br />

26 select node,pred from shortestpaths($1)<br />

27 )<br />

28 ) s1 $$<br />

29 STABLE LANGUAGE SQL;<br />

30<br />

31 SELECT nodea,SUM(u) as summe FROM<br />

32 ( SELECT time,nodea,unterschied_skip1(time,nodea) as u FROM (<br />

33 -- 200733 zeilen<br />

34 SELECT dist<strong>in</strong>ct t.time,nodea FROM times t, l<strong>in</strong>ks l<br />

35 WHERE t.time BETWEEN ’2010-09-14 00:00:00’ and ’2010-09-15 00:00:00’<br />

36 AND t.time = l.time<br />

37 ) as s1<br />

38 ) as s2<br />

39 GROUP BY nodea ORDER BY summe DESC;<br />

List<strong>in</strong>g 5.3: Abfrage <strong>der</strong> Routenän<strong>der</strong>ungen beim Ausfall e<strong>in</strong>es Knotens<br />

© Andreas Redmer — 29. September 2011 73


5. Testläufe - Beispiele für Datenabfragen<br />

Ausgabe: 146 Zeilen (die ersten zehn Zeilen s<strong>in</strong>d <strong>in</strong> Tabelle 5.1 angegeben)<br />

Nr. Knoten Summe Nr. Knoten Summe<br />

1 192.168.0.254 94170 6 192.168.1.112 27606<br />

2 192.168.1.111 57870 7 192.168.1.155 27247<br />

3 192.168.1.184 35342 8 192.168.1.214 26729<br />

4 192.168.1.93 33846 9 192.168.1.180 26691<br />

5 10.2.0.247 31915 10 192.168.2.7 25120<br />

Tabelle 5.1.: Erste Ergebnisse <strong>der</strong> SQL-Abfrage <strong>in</strong> List<strong>in</strong>g 5.3<br />

Laufzeit: ≈ 121 M<strong>in</strong>uten ( ≈ 2 Stunden)<br />

Erklärung: Die Anfrage <strong>in</strong> List<strong>in</strong>g 5.3 löst e<strong>in</strong> <strong>in</strong> Abschnitt 1.4 beschriebenes<br />

Problem <strong>der</strong> Problemklasse C. Sie streicht vor <strong>der</strong> Ausführung des Dijkstra-<br />

Algorithmus e<strong>in</strong>en Knoten aus dem Graphen (dies simuliert den Ausfall des<br />

Knotens) und führt dies für jeden im Graphen vorhandenen Knoten aus. Die<br />

Komplexität erhöht sich da<strong>durch</strong> um den Faktor O(n). Die Ausgabe ist die<br />

Anzahl <strong>der</strong> Unterschiede, die e<strong>in</strong> Knoten <strong>in</strong> den Routen verursacht, wenn er<br />

ausfällt. Dies kann <strong>durch</strong>aus als e<strong>in</strong> Maß für die Wichtigkeit e<strong>in</strong>es Knotens<br />

verwendet werden. Es ist allerd<strong>in</strong>gs zu beachten, dass an dieser Stelle die Existenz<br />

und Qualität <strong>der</strong> Alternativrouten nicht betrachtet wird und somit <strong>der</strong><br />

entstehende Nachteil <strong>durch</strong> die verän<strong>der</strong>ten Routen unbestimmt ist.<br />

Wie bereits zu Beg<strong>in</strong>n des Abschnitts 3.2 erklärt, wurde die PL/pgSQL-<br />

Funktion shortestPaths auch als Vorlage verwendet, um weitere UDF zu<br />

entwickeln, die ähnliche Probleme lösen. Diese wurde hier kopiert und es wurde<br />

die neue Funktion shortestpaths_skip1 erstellt. Diese hat den selben Ablauf<br />

wie das Orig<strong>in</strong>al: alle Gateways h<strong>in</strong>zufügen 16 , alle L<strong>in</strong>ks h<strong>in</strong>zufügen, ausführen<br />

und Ergebnis zurückgeben 17 . PERFORM funktioniert übrigens genau wie die<br />

SQL-Anweisung SELECT. Sie weist den Interpreter jedoch an, das Ergebnis <strong>der</strong><br />

Abfrage zu ignorieren. H<strong>in</strong>zugefügt wurden nun also lediglich e<strong>in</strong> Parameter<br />

(e<strong>in</strong>e IP-Adresse e<strong>in</strong>es Knotens) und e<strong>in</strong>e zusätzliche Bed<strong>in</strong>gung <strong>in</strong> <strong>der</strong> WHE-<br />

RE-Klausel (Zeile 12), die dafür sorgt, dass ke<strong>in</strong>e Kanten, die diesen Knoten<br />

be<strong>in</strong>halten, <strong>in</strong> <strong>der</strong> Java Klasse registriert werden. Der Knoten wird vollständig<br />

ignoriert.<br />

Analog zu List<strong>in</strong>g 5.2 wird auch hier wie<strong>der</strong> e<strong>in</strong>e Unterschiedsfunktion erstellt.<br />

Sie heißt unterschied_skip1 (beg<strong>in</strong>nend <strong>in</strong> Zeile 19) und berechnet<br />

e<strong>in</strong> Maß für den Unterschied für die normalerweise geltenden Routen und die<br />

16 Für die Gateways gibt es noch ke<strong>in</strong>e Tabelle, deshalb werden sie alle konstant <strong>in</strong> die<br />

Funktion e<strong>in</strong>getragen.<br />

17 Die orig<strong>in</strong>ale shortestpaths-Funktion hat noch e<strong>in</strong> Exception-Handl<strong>in</strong>g, was hier aus<br />

Platzgründen weggelassen wurde.<br />

© Andreas Redmer — 29. September 2011 74


5. Testläufe - Beispiele für Datenabfragen<br />

Routen wenn e<strong>in</strong> def<strong>in</strong>ierter Knoten ausfällt. Sei hier D V die Ausgabemenge<br />

des Dijkstra-Algorithmus auf <strong>der</strong> Knotenmenge V . Dann erzeugt <strong>der</strong> Knoten<br />

u ∈ V e<strong>in</strong>en Unterschied von<br />

wenn er ausfällt.<br />

|(D V \ D V \{u} ) ∪ (D V \{u} \ D V )|<br />

Die <strong>in</strong>nere Abfrage (s1) selektiert zunächst alle am 14.09.2010 aufgezeichneten<br />

Timestamps und Knoten. In Zeile 34 wird das Kreuzprodukt <strong>der</strong> beiden<br />

Mengen (also Timestamps × Knoten) gebildet. Für die spätere Schätzung <strong>der</strong><br />

Laufzeit ist es immer hilfreich, mit <strong>der</strong> Aggregatfunktion COUNT die Elemente<br />

<strong>der</strong> <strong>in</strong>neren Abfragen zu zählen. In diesem Fall ergeben sich 200733 Zeilen aus<br />

<strong>der</strong> <strong>in</strong>neren Abfrage. In <strong>der</strong> Abfrage s2 wird nun für jedes Paar die Funktion<br />

unterschied_skip1 aufgerufen. Es wird also <strong>in</strong> jedem Timestamp je<strong>der</strong><br />

Knoten e<strong>in</strong>mal ignoriert. Die äußere Abfrage summiert alle ermittelten Unterschiede<br />

pro Knoten auf und sortiert die Knoten mit <strong>der</strong> größten Summe nach<br />

ganz oben.<br />

Die Laufzeit kann im Vorfeld wie zuvor mit<br />

abgeschätzt werden.<br />

200733 · 4 · 9ms = 7226388ms ≈ 120m<strong>in</strong><br />

Fazit: Basierend auf den aufgezeichneten Daten am 14.09.2010, würde <strong>der</strong><br />

Gateway-Knoten 192.168.0.254 mit Abstand die meisten Verän<strong>der</strong>ungen <strong>in</strong><br />

den Routen hervorrufen, wenn er ausfallen würde. Er bildet zusammen mit dem<br />

Gateway 10.2.0.247 und den Nicht-Gateways 192.168.1.111, 192.168.1.184<br />

und 192.168.1.93 die Top 5 an diesem Tag. Weiterh<strong>in</strong> ist zu erkennen, dass<br />

je<strong>der</strong> <strong>der</strong> 146 Knoten, des gesamten Netzwerkes (<strong>in</strong> dem Zeitraum), Routenverän<strong>der</strong>ungen<br />

verursacht wenn er ausfällt, da die Summe immer größer als<br />

0 ist. Das kommt daher, dass die Routenverän<strong>der</strong>ung immer für das gesamte<br />

Netzwerk summiert ist, egal für wie viele Knoten die Än<strong>der</strong>ung tatsächlich<br />

relevant ist.<br />

© Andreas Redmer — 29. September 2011 75


5. Testläufe - Beispiele für Datenabfragen<br />

5.4. Routenän<strong>der</strong>ungen bei Ausfall zweier Knoten<br />

1 CREATE OR REPLACE FUNCTION shortestpaths_skip2(timestamptz, <strong>in</strong>et, <strong>in</strong>et)<br />

2 RETURNS SETOF PathElement AS $$ DECLARE<br />

3 BEGIN<br />

4 PERFORM truncateRootNodes();<br />

5 PERFORM addRootNode (host(’192.168.0.254’::<strong>in</strong>et));<br />

6 PERFORM addRootNode (host(’10.2.0.251’::<strong>in</strong>et));<br />

7 PERFORM addRootNode (host(’10.2.0.247’::<strong>in</strong>et));<br />

8 PERFORM addRootNode (host(’192.168.0.251’::<strong>in</strong>et));<br />

9 PERFORM truncateL<strong>in</strong>ks();<br />

10 PERFORM addL<strong>in</strong>k(host(nodea), host(nodeb), lq, lqn) from l<strong>in</strong>ks<br />

11 where time=$1 AND nodea!=$2 AND nodeb!=$2 AND nodea!=$3 AND nodeb!=$3;<br />

12<br />

13 PERFORM run();<br />

14 RETURN QUERY SELECT node,pred,d from getPathSet();<br />

15 END; $$ STABLE LANGUAGE plpgsql;<br />

16<br />

17 CREATE OR REPLACE FUNCTION unterschied_skip2(timestamptz,<strong>in</strong>et,<strong>in</strong>et)<br />

18 RETURNS big<strong>in</strong>t AS $$<br />

19 select count (*) from<br />

20 ( ( select node,pred from shortestpaths($1) EXCEPT<br />

21 select node,pred from shortestpaths_skip2($1,$2,$3)<br />

22 ) UNION<br />

23 ( select node,pred from shortestpaths_skip2($1,$2,$3)<br />

24 EXCEPT select node,pred from shortestpaths($1)<br />

25 )<br />

26 ) s1 $$ STABLE LANGUAGE SQL;<br />

27<br />

28 select n,m,SUM(u) as Summe from<br />

29 ( SELECT time,n,m,unterschied_skip2(time,n,m) as u FROM (<br />

30 SELECT dist<strong>in</strong>ct t.time,n1.nodea as n,n2.nodea as m FROM times t, (<br />

31 SELECT dist<strong>in</strong>ct l1.time,l1.nodea FROM l<strong>in</strong>ks l1<br />

32 WHERE l1.time BETWEEN ’2010-09-14 00:00:00’ and ’2010-09-15 00:00:00’<br />

33 AND EXTRACT(m<strong>in</strong>ute FROM l1.time)=0<br />

34 ) as n1, (<br />

35 SELECT dist<strong>in</strong>ct l2.time,l2.nodea FROM l<strong>in</strong>ks l2<br />

36 WHERE l2.time BETWEEN ’2010-09-14 00:00:00’ and ’2010-09-15 00:00:00’<br />

37 AND EXTRACT(m<strong>in</strong>ute FROM l2.time)=0<br />

38 ) as n2<br />

39 WHERE t.time = n1.time AND t.time = n2.time AND n1.nodea


5. Testläufe - Beispiele für Datenabfragen<br />

Ausgabe: ist <strong>in</strong> Tabelle 5.2 dargestellt<br />

n m Summe<br />

192.168.0.254 192.168.1.111 2314<br />

10.2.0.247 192.168.0.254 2084<br />

192.168.0.254 192.168.1.155 1988<br />

192.168.0.254 192.168.1.180 1988<br />

192.168.0.254 192.168.1.69 1964<br />

192.168.0.254 192.168.1.93 1940<br />

192.168.0.254 192.168.1.143 1894<br />

192.168.0.254 192.168.1.245 1878<br />

192.168.0.254 192.168.1.188 1842<br />

192.168.0.254 192.168.1.27 1818<br />

Tabelle 5.2.: Die Ergebnisse <strong>der</strong> SQL-Abfrage <strong>in</strong> List<strong>in</strong>g 5.4<br />

Laufzeit: ≈ 143 M<strong>in</strong>uten ( ≈ 2,4 Stunden)<br />

Erklärung: Die Anfrage <strong>in</strong> List<strong>in</strong>g 5.4 löst e<strong>in</strong> Problem <strong>der</strong> Problemklasse<br />

C. Sie streicht vor <strong>der</strong> Ausführung des Dijkstra-Algorithmus zwei Knoten<br />

aus dem Graphen (dies simuliert den Ausfall <strong>der</strong> Knoten) und führt dies für<br />

jedes mögliche im Graphen vorhandene Paar von Knoten aus. Die Komplexität<br />

erhöht sich da<strong>durch</strong> um den Faktor O(n 2 ). Die Ausgabe ist die Anzahl<br />

<strong>der</strong> Unterschiede, die <strong>in</strong> den Routen entstehen, wenn sie gleichzeitig ausfallen.<br />

Hier kann also geprüft werden, ob es e<strong>in</strong>e Komb<strong>in</strong>ation von Knoten gibt, die<br />

bei gleichzeitigem Ausfall sehr gravierende Än<strong>der</strong>ungen im Netzwerk bewirkt.<br />

Zunächst wurde wie<strong>der</strong> e<strong>in</strong>e neue Variante <strong>der</strong> shortestpaths-Funktion erstellt,<br />

die jedoch nun zwei Knoten ignorieren kann. Die Funktion heißt shortestpaths_skip2,<br />

enthält e<strong>in</strong>en zusätzlichen Parameter (den zusätzlichen Knoten)<br />

und <strong>in</strong> Zeile 11 e<strong>in</strong>e angepasste Bed<strong>in</strong>gung <strong>in</strong> <strong>der</strong> WHERE Klausel. Alle Kanten,<br />

die von und zu diesen beiden Knoten führen, werden dort ignoriert. Wie<br />

dort schon zu erkennen ist, hätte man <strong>der</strong> Funktion auch e<strong>in</strong> Array <strong>der</strong> Größe<br />

x übergeben können. Jedoch ist es wahrsche<strong>in</strong>lich nicht sehr <strong>in</strong>teressant, was<br />

passiert, wenn x Knoten gleichzeitig ausfallen. Weiterh<strong>in</strong> riskiert man dabei<br />

alle Gateways zu ignorieren, was e<strong>in</strong>e <strong>der</strong> Vorbed<strong>in</strong>gungen für den Dijkstra-<br />

Algorithmus elim<strong>in</strong>ieren würde. Es muss immer e<strong>in</strong>en Startknoten, also m<strong>in</strong>destens<br />

e<strong>in</strong> Gateway, geben.<br />

Analog zu List<strong>in</strong>g 5.3 wird auch hier wie<strong>der</strong> e<strong>in</strong>e Unterschiedsfunktion erstellt.<br />

Diese heißt unterschied_skip2 (beg<strong>in</strong>nend <strong>in</strong> Zeile 17) und berechnet<br />

e<strong>in</strong> Maß für den Unterschied zwischen den normalerweise geltenden Routen<br />

und den Routen, die gelten wenn zwei def<strong>in</strong>ierte Knoten gleichzeitig ausfallen.<br />

© Andreas Redmer — 29. September 2011 77


5. Testläufe - Beispiele für Datenabfragen<br />

Sei hier D V die Ausgabemenge des Dijkstra-Algorithmus auf <strong>der</strong> Knotenmenge<br />

V . Dann erzeugen die Knoten u, w ∈ V e<strong>in</strong>en Unterschied von<br />

wenn sie gleichzeitig ausfallen.<br />

|(D V \ D V \{u,w} ) ∪ (D V \{u,w} \ D V )|<br />

Die erste <strong>in</strong>nere Abfrage (n1) selektiert zunächst alle Timestamps und Knoten<br />

(ohne Duplikate), die am 14.09.2010 (Zeile 32) und zu e<strong>in</strong>er vollen Stunde<br />

(Zeile 33) aufgezeichnet wurden. Genau das Gleiche wird <strong>in</strong> n2 (Zeile 35 bis<br />

38) nochmal gemacht, um die selbe Knotenmenge erneut zu erhalten. In Zeile<br />

39 wird das Kreuzprodukt (Timestamps × Knoten × Knoten) gebildet.<br />

Gleichzeitig sorgt die WHERE-Klausel <strong>in</strong> dieser Zeile dafür, dass die IP-Adresse<br />

des ersten Knotens immer kle<strong>in</strong>er ist als die des zweiten. Da<strong>durch</strong> wird nicht<br />

nur ausgeschlossen, dass beide gleich s<strong>in</strong>d, son<strong>der</strong>n es werden auch Duplikate <strong>in</strong><br />

<strong>der</strong> Ergebnismenge verh<strong>in</strong><strong>der</strong>t, da sonst jedes Paar doppelt vorkommen würde.<br />

Das passiert, weil Zeile 11 beide Knoten ignoriert, egal <strong>in</strong> welcher Reihenfolge<br />

sie übergeben werden. Somit wird <strong>in</strong> Zeile 29, für jeden Timestamp und<br />

für jedes mögliche Paar von Knoten, e<strong>in</strong>mal die Funktion unterschied_skip2<br />

aufgerufen. Die äußere Abfrage summiert alle ermittelten Unterschiede pro<br />

Knotenpaar auf und sortiert die höchsten Summen nach oben.<br />

Wenn im Vorfeld mit <strong>der</strong> Aggregatfunktion COUNT die Elemente des Kreuzproduktes<br />

aus s1 gezählt werden, erhält man <strong>in</strong> diesem Fall die Zahl 231888.<br />

Genau so oft wird die Funktion unterschied_skip2 aufgerufen. Die Laufzeit<br />

kann also mit<br />

231888 · 4 · 9ms = 8347968ms ≈ 139m<strong>in</strong><br />

abgeschätzt werden.<br />

Fazit: Basierend auf den am 14.09.2010 zur vollen Stunde aufgezeichneten<br />

Daten, würden die Knoten 192.168.0.254 und 192.168.1.111 die meisten Routen<br />

verän<strong>der</strong>n, wenn sie gleichzeitig ausfallen würden. Dabei ist Letzterer ke<strong>in</strong><br />

Gateway. Erst an zweiter Stelle kommt das Gatewaypaar 192.168.0.254 und<br />

10.2.0.247. Der Knoten 192.168.0.254 ist <strong>in</strong> <strong>der</strong> gesamten Top 10 Liste <strong>in</strong><br />

jedem Paar enthalten.<br />

© Andreas Redmer — 29. September 2011 78


5. Testläufe - Beispiele für Datenabfragen<br />

5.5. Knoten die häufig auf Routen liegen<br />

1 CREATE OR REPLACE FUNCTION path(<strong>in</strong>et,varchar[]) RETURNS <strong>in</strong>et[] AS $$<br />

2 DECLARE p INET; pa INET[];<br />

3 BEGIN<br />

4 SELECT m<strong>in</strong>(pred) INTO p FROM (<br />

5 SELECT (str<strong>in</strong>g_to_array(unnest, ’,’))[1] as node,<br />

6 (str<strong>in</strong>g_to_array(unnest, ’,’))[2] as pred FROM unnest($2)<br />

7 ) as s1<br />

8 WHERE node = host($1);<br />

9 IF p IS NOT NULL THEN pa = pa || path(p,$2); END IF;<br />

10 RETURN p||pa;<br />

11 END;<br />

12 $$ STABLE LANGUAGE plpgsql;<br />

13<br />

14 SELECT s4.n, count(s4.n) FROM<br />

15 (<br />

16 select s1.time, s2.nodea,<br />

17 path (s2.nodea,array_agg(host(node) ||’,’|| host(pred)))<br />

18 from (<br />

19 select t.time,(shortestpaths(t.time)).* from times t<br />

20 where extract (m<strong>in</strong>ute from time)=0<br />

21 AND extract (year from time)=2011<br />

22 AND extract (DOW from time)=1<br />

23 ) as s1,<br />

24 (<br />

25 select dist<strong>in</strong>ct nodea from l<strong>in</strong>ks<br />

26 where extract (m<strong>in</strong>ute from time)=0<br />

27 AND extract (year from time)=2011<br />

28 AND extract (DOW from time)=1<br />

29 ) as s2<br />

30 GROUP BY s1.time, s2.nodea<br />

31 ) as s3,<br />

32 (<br />

33 select dist<strong>in</strong>ct nodea as n from l<strong>in</strong>ks<br />

34 where extract (m<strong>in</strong>ute from time)=0<br />

35 AND extract (year from time)=2011<br />

36 AND extract (DOW from time)=1<br />

37 ) as s4<br />

38 where s3.path @> ARRAY[s4.n]<br />

39 GROUP BY s4.n ORDER BY count DESC LIMIT 10;<br />

List<strong>in</strong>g 5.5: Abfrage wie oft e<strong>in</strong> Knoten auf e<strong>in</strong>er Route liegt<br />

© Andreas Redmer — 29. September 2011 79


5. Testläufe - Beispiele für Datenabfragen<br />

Ausgabe: ist <strong>in</strong> Tabelle 5.3 dargestellt<br />

n<br />

count<br />

10.2.0.247 21217<br />

192.168.0.254 20284<br />

192.168.1.93 9872<br />

192.168.1.111 9667<br />

192.168.2.7 9039<br />

192.168.2.3 8732<br />

192.168.1.182 5489<br />

192.168.1.184 5170<br />

192.168.1.187 4982<br />

192.168.1.155 4826<br />

Tabelle 5.3.: Die Ergebnisse <strong>der</strong> SQL-Abfrage <strong>in</strong> List<strong>in</strong>g 5.5<br />

Laufzeit: ≈ 7 M<strong>in</strong>uten<br />

Erklärung: In Abschnitt 5.3 wurde angenommen, dass e<strong>in</strong> Knoten wichtig<br />

ist, wenn er beim Ausfall viele Routenverän<strong>der</strong>ungen verursacht. Die Abfragen<br />

dauerten recht lange. Im List<strong>in</strong>g 5.5 wird nun mit weniger Zeitaufwand e<strong>in</strong><br />

an<strong>der</strong>es Maß berechnet. Es gibt an, wie oft e<strong>in</strong> Knoten (direkt o<strong>der</strong> <strong>in</strong>direkt)<br />

für die Internetverb<strong>in</strong>dung e<strong>in</strong>es an<strong>der</strong>en Knotens notwendig ist (also wie oft<br />

er auf dessen Route liegt). Je mehr Knoten hierarchisch von e<strong>in</strong>em Knoten<br />

abhängig s<strong>in</strong>d, desto wichtiger wird er. Theoretisch können die hier gefundenen<br />

Knoten auch als Vorauswahl <strong>in</strong> Abschnitt 5.3 und 5.4 verwendet werden, um<br />

die dortige Knotenmenge zu reduzieren.<br />

Zuerst wird hier e<strong>in</strong>e rekursive Funktion namens path angelegt, die alle Knoten,<br />

die auf dem Weg zum nächsten Gateway liegen, für e<strong>in</strong>en Knoten zurück<br />

gibt. Sie hat die Komplexität O(log(n)). Die Rückgabe erfolgt als Array von<br />

IP-Adressen. Als Parameter bekommt sie den Ausgangsknoten, für den <strong>der</strong><br />

Weg ermittelt werden soll, und das Ergebnis des Dijkstra-Algorithmus. Wie<br />

im Abschnitt 3.1.3 schon erwähnt, gibt es <strong>in</strong> PL/pgSQL praktisch ke<strong>in</strong>e mehrdimensionalen,<br />

dynamischen Arrays. Wesentlich e<strong>in</strong>facher als e<strong>in</strong> Workaround,<br />

ist es den Knoten und dessen Vorgänger e<strong>in</strong>fach per Komma getrennt <strong>in</strong> e<strong>in</strong>er<br />

Zeichenkette zu speichern. Somit ist <strong>der</strong> zweite Parameter <strong>der</strong> Funktion e<strong>in</strong><br />

Array von Str<strong>in</strong>gs. In diesem Dijkstra-Ergebnis wird <strong>der</strong> Vorgänger des gegebenen<br />

Knotens gesucht und an die Ausgabe angehängt. Dies geschieht rekursiv<br />

so lange, bis ke<strong>in</strong> weiterer Vorgänger auff<strong>in</strong>dbar ist.<br />

Aufgrund des Geschw<strong>in</strong>digkeitsvorteils (<strong>durch</strong> die ger<strong>in</strong>ge Komplextität)<br />

kann nun e<strong>in</strong> größerer Zeitraum zur Analyse gewählt werden, als <strong>in</strong> Abschnitt<br />

5.4. Die erste <strong>in</strong>nere Selektion (s1) wählt alle Timestamps und die zugehöri-<br />

© Andreas Redmer — 29. September 2011 80


5. Testläufe - Beispiele für Datenabfragen<br />

gen Ergebnisse des Dijkstra-Algorithmus von allen Daten aus, die an e<strong>in</strong>em<br />

Montag (Zeile 22), im Jahre 2011 (Zeile 21) und zu e<strong>in</strong>er vollen Stunde (Zeile<br />

20) aufgezeichnet wurden. Die Abfrage s2 wählt Knoten (ohne Duplikate) aus,<br />

die <strong>in</strong> diesem Zeitraum vorhanden s<strong>in</strong>d. Die Abfrage s3 bildet das Kreuzprodukt<br />

(Timestamps × Knoten) und ruft für jedes erhaltene Paar die Funktion<br />

path auf (Zeile 17). Die Ausgabe von s3 ist also <strong>der</strong> Pfad, von jedem Knoten<br />

zu jedem Zeitpunkt, <strong>in</strong>s Internet. In s4 werden nochmal alle Knoten <strong>in</strong> dem<br />

selben Zeitraum selektiert. Die Abfrage, die <strong>in</strong> Zeile 14 beg<strong>in</strong>nt, fügt s3 und<br />

s4 mit e<strong>in</strong>em Inner Jo<strong>in</strong> zusammen, wobei die Bed<strong>in</strong>gung für die Zusammenführung<br />

ist, dass <strong>der</strong> Knoten aus s4 <strong>in</strong> dem Pfad aus s3 vorhanden ist (Zeile<br />

38). Der Operator @> <strong>der</strong> dabei verwendet wird, ist <strong>in</strong> PostgreSQL <strong>der</strong> conta<strong>in</strong>s-Operator<br />

für Arrays. In Zeile 14 wird nun gezählt, wie oft je<strong>der</strong> Knoten<br />

<strong>in</strong> diesem Jo<strong>in</strong> vorkam. Das Ergebnis wird nach diesem Zähler sortiert.<br />

Fazit: Basierend auf den Daten, die an allen Montagen im Jahr 2011, zu<br />

e<strong>in</strong>er vollen Stunde aufgezeichnet wurden, ist das Gateway 10.2.0.247 auf den<br />

meisten Routen vorhanden. An zweiter Stelle liegt das Gateway 192.168.0.254.<br />

Die Nicht-Gateways 192.168.1.93, 192.168.1.111 und 192.168.2.7 s<strong>in</strong>d die drei<br />

aktivsten Router bezüglich <strong>der</strong> Anzahl <strong>der</strong> von Ihnen abhängigen Teilnehmer.<br />

Ohne das beschränkende LIMIT 10 am Ende, gibt die Abfrage 129 Knoten zurück,<br />

die <strong>in</strong> diesem Zeitraum überhaupt auf e<strong>in</strong>er Route liegen. Alle an<strong>der</strong>en<br />

Knoten s<strong>in</strong>d nichtroutende Teilnehmer. Die Anzahl <strong>der</strong> Teilnehmer die zu diesen<br />

Zeitpunkten überhaupt im Netzwerk ist beträgt 168. Dies ergibt sich wenn<br />

die Ausgabe <strong>der</strong> Abfrage s4 gezählt wird.<br />

© Andreas Redmer — 29. September 2011 81


5. Testläufe - Beispiele für Datenabfragen<br />

5.6. Wichtige Knoten und Kanten<br />

1 -- Abfrage 1: Wie oft ist e<strong>in</strong> Knoten Vorgaenger ?<br />

2 select pred, count(d) from (<br />

3 select (shortestpaths(time)).* from times<br />

4 where time between (select max(time) from times) - <strong>in</strong>terval’14 days’<br />

5 and (select max(time) from times)<br />

6 ) as s1<br />

7 GROUP BY pred ORDER BY count DESC LIMIT 10<br />

8<br />

9 -- Abfrage 2: Wie oft ist e<strong>in</strong>e Kante <strong>in</strong> den Routen enthalten ?<br />

10 select node, pred, count(d) from (<br />

11 select (shortestpaths(time)).* from times<br />

12 where time between (select max(time) from times) - <strong>in</strong>terval’14 days’<br />

13 and (select max(time) from times)<br />

14 ) as s1<br />

15 GROUP BY node, pred ORDER BY count DESC LIMIT 10<br />

List<strong>in</strong>g 5.6: Abfragen für Wichtigkeit von Knoten und Kanten<br />

Ausgaben: s<strong>in</strong>d <strong>in</strong> Tabelle 5.4 dargestellt<br />

Abfrage 1 Abfrage 2<br />

pred count node pred count<br />

192.168.1.69 242885 192.168.2.3 192.168.2.7 20095<br />

10.2.0.247 233442 192.168.1.52 192.168.2.3 20095<br />

192.168.0.254 201877 192.168.2.4 192.168.2.3 20095<br />

192.168.1.184 155214 192.168.10.2 192.168.0.254 20095<br />

192.168.1.214 145658 192.168.10.3 192.168.0.254 20095<br />

192.168.2.3 120120 192.168.2.7 192.168.0.254 20094<br />

192.168.1.208 107194 192.168.2.6 192.168.2.5 20094<br />

192.168.1.199 97647 192.168.1.126 192.168.2.3 20094<br />

192.168.1.241 96574 192.168.2.5 192.168.2.8 20094<br />

192.168.1.183 93700 192.168.2.8 192.168.0.254 20094<br />

Tabelle 5.4.: Die Ergebnisse <strong>der</strong> SQL-Abfragen <strong>in</strong> List<strong>in</strong>g 5.6<br />

Laufzeiten: jeweils ≈ 9 M<strong>in</strong>uten<br />

Erklärung: In Abschnitt 5.3 wurde angenommen, dass e<strong>in</strong> Knoten wichtig<br />

ist, wenn er beim Ausfall viele Routenverän<strong>der</strong>ungen verursacht. In Abschnitt<br />

5.5 wurde die Wichtigkeit nach <strong>der</strong> Anzahl <strong>der</strong> direkt und <strong>in</strong>direkt abhängigen<br />

Knoten bestimmt. In List<strong>in</strong>g 5.6 (Abfrage 1) wurde nun e<strong>in</strong>e noch e<strong>in</strong>fachere<br />

Abfrage verwendet, die e<strong>in</strong>fach zählt, wie häufig e<strong>in</strong> Knoten als Vorgänger e<strong>in</strong>es<br />

an<strong>der</strong>en Knoten vorkommt. Dies ist nur e<strong>in</strong> bed<strong>in</strong>gtes Maß für die Wichtigkeit,<br />

© Andreas Redmer — 29. September 2011 82


5. Testläufe - Beispiele für Datenabfragen<br />

da die gefundenen Knoten sehr weit unten <strong>in</strong> <strong>der</strong> Hierarchie se<strong>in</strong> können. Somit<br />

s<strong>in</strong>d sie nicht für das gesamte Netzwerk, son<strong>der</strong>n nur für die Knoten <strong>in</strong> <strong>der</strong><br />

direkten Nachbarschaft wichtig. Theoretisch könnte die Abfrage aus List<strong>in</strong>g<br />

5.5 als Vorauswahl dienen wichtige Knoten zu f<strong>in</strong>den, wobei die Abfrage 1 aus<br />

List<strong>in</strong>g 5.6 dann die Qualität und Verfügbarkeit <strong>der</strong>en Nachbarschaftsknoten<br />

prüfen könnte.<br />

In Zeile 4 werden alle Timestamps ausgewählt, die im Zeitraum <strong>der</strong> letzten<br />

14 aufgezeichneten Tage (genau 14 mal 24 Stunden) liegen. Die Abfrage s1<br />

führt die shortestPaths-Funktion für diese Timestamps aus. Abfrage 1 zählt<br />

dann, wie oft e<strong>in</strong> Knoten als Vorgänger vorgekommen ist.<br />

In Abfrage 2 <strong>in</strong> List<strong>in</strong>g 5.6 wird genau das selbe für Kanten gemacht. Es<br />

zeigt also an, wie häufig e<strong>in</strong>e Kante <strong>in</strong> den Pfaden vorkam. Man erkennt hier<br />

auch schnell, wie e<strong>in</strong>fach alle Maße die zuvor für ”<br />

wichtige“ Knoten verwendet<br />

wurden auf e<strong>in</strong>fache Weise auch für Kanten verwendet werden können. Weitere<br />

Beispiele folgen dazu noch <strong>in</strong> den folgenden Abschnitten.<br />

Fazit: In den letzten 14 Tagen <strong>der</strong> Datenaufzeichnungen war <strong>der</strong> Knoten<br />

192.168.1.69 für se<strong>in</strong>e direkte Nachbarschaft sehr wichtig, da er sehr häufig <strong>der</strong><br />

erste Schritt auf dem Pfad <strong>in</strong>s Internet war. Das Gateway 10.2.0.247 lag dabei<br />

auf Platz zwei. In Abbildung 5.2 werden die zehn Kanten, die hier als wichtig<br />

ermittelt wurden, grafisch dargestellt. Das Präfix ”<br />

192.168.“ wurde dabei<br />

aus Platzgründen weggelassen. Allerd<strong>in</strong>gs ist dieses Maß (für diesen Zeitraum)<br />

nicht sehr aussagekräftig, denn die Werte liegen alle sehr dicht beie<strong>in</strong>an<strong>der</strong><br />

(vgl. Tabelle 5.4).<br />

✞ ☎<br />

✝0.254<br />

✆<br />

✞ ☎<br />

✝2.7<br />

✆<br />

✞ ☎<br />

✝2.3<br />

✆<br />

✞ ☎<br />

✝1.126<br />

✆<br />

✞ ☎<br />

✝10.3<br />

✆<br />

✞ ☎<br />

✝10.2<br />

✆<br />

✞ ☎<br />

✝1.52<br />

✆<br />

✞ ☎<br />

✝2.4<br />

✆<br />

✞ ☎<br />

✝2.8<br />

✆<br />

✞ ☎<br />

✝2.5<br />

✆<br />

✞ ☎<br />

✝2.6<br />

✆<br />

Abbildung 5.2.: Graph <strong>der</strong> Kanten aus Tabelle 5.4<br />

© Andreas Redmer — 29. September 2011 83


5. Testläufe - Beispiele für Datenabfragen<br />

5.7. Routenän<strong>der</strong>ungen bei Ausfall e<strong>in</strong>er Kante<br />

1 CREATE OR REPLACE FUNCTION shortestpaths_edge(timestamptz, <strong>in</strong>et, <strong>in</strong>et)<br />

2 RETURNS SETOF PathElement AS $$ DECLARE<br />

3 BEGIN<br />

4 PERFORM truncateRootNodes();<br />

5 PERFORM addRootNode (host(’192.168.0.254’::<strong>in</strong>et));<br />

6 PERFORM addRootNode (host(’10.2.0.251’::<strong>in</strong>et));<br />

7 PERFORM addRootNode (host(’10.2.0.247’::<strong>in</strong>et));<br />

8 PERFORM addRootNode (host(’192.168.0.251’::<strong>in</strong>et));<br />

9 PERFORM truncateL<strong>in</strong>ks();<br />

10 PERFORM addL<strong>in</strong>k(host(nodea), host(nodeb), lq, lqn) from l<strong>in</strong>ks<br />

11 where time=$1<br />

12 AND NOT((nodea=$2 AND nodeb=$3) OR (nodea=$3 AND nodeb=$2));<br />

13<br />

14 PERFORM run();<br />

15 RETURN QUERY SELECT node,pred,d from getPathSet();<br />

16 END; $$ STABLE LANGUAGE plpgsql;<br />

17<br />

18 CREATE OR REPLACE FUNCTION unterschied_edge(timestamptz,<strong>in</strong>et,<strong>in</strong>et)<br />

19 RETURNS big<strong>in</strong>t AS $$<br />

20 select count (*) from<br />

21 ( ( select node,pred from shortestpaths($1) EXCEPT<br />

22 select node,pred from shortestpaths_edge($1,$2,$3) )<br />

23 UNION<br />

24 ( select node,pred from shortestpaths_edge($1,$2,$3)<br />

25 EXCEPT select node,pred from shortestpaths($1) )<br />

26 ) s1 $$<br />

27 STABLE LANGUAGE SQL;<br />

28<br />

29 select n,m,SUM(u) as Summe from (<br />

30 SELECT time,n,m,unterschied_edge(time,n,m) as u FROM (<br />

31 -- 10074 zeilen<br />

32 SELECT dist<strong>in</strong>ct time, nodea as n, nodeb as m FROM l<strong>in</strong>ks<br />

33 WHERE time >= ’2011-03-11 00:00:00’<br />

34 AND extract(m<strong>in</strong>ute FROM time)=0<br />

35 AND extract(DOW FROM time) IN (0,6)<br />

36 AND extract(hour FROM time) between 15 and 18<br />

37 ) as s1<br />

38 ) as s2<br />

39 GROUP BY n,m ORDER BY Summe DESC LIMIT 10;<br />

List<strong>in</strong>g 5.7: Abfrage nach Routenän<strong>der</strong>ungen <strong>durch</strong> Wegfall e<strong>in</strong>er Kante<br />

© Andreas Redmer — 29. September 2011 84


5. Testläufe - Beispiele für Datenabfragen<br />

Ausgabe: ist <strong>in</strong> Tabelle 5.5 dargestellt<br />

n m Summe<br />

192.168.1.93 192.168.1.111 522<br />

192.168.1.111 192.168.1.197 516<br />

192.168.1.155 192.168.1.197 492<br />

192.168.1.155 192.168.1.166 468<br />

192.168.1.111 192.168.1.112 447<br />

192.168.1.180 192.168.1.187 431<br />

192.168.1.69 192.168.1.180 415<br />

192.168.1.112 192.168.1.214 400<br />

192.168.2.3 192.168.2.7 366<br />

192.168.1.182 192.168.1.184 290<br />

Tabelle 5.5.: Das Ergebnis <strong>der</strong> SQL-Abfrage <strong>in</strong> List<strong>in</strong>g 5.7<br />

Laufzeit: 367 Sekunden ( ≈ 6 M<strong>in</strong>uten)<br />

Erklärung: Die Anfrage <strong>in</strong> List<strong>in</strong>g 5.7 löst e<strong>in</strong> Problem <strong>der</strong> Problemklasse<br />

C, ist jedoch auch <strong>der</strong> erste Schritt beim F<strong>in</strong>den von Flaschenhälsen (Problemklasse<br />

D). Sie streicht vor <strong>der</strong> Ausführung des Dijkstra-Algorithmus e<strong>in</strong>e<br />

Kante aus dem Graphen (dies simuliert den Ausfall <strong>der</strong> Kante) und führt dies<br />

für jede Kante im Graphen aus. Die Komplexität erhöht sich da<strong>durch</strong> um den<br />

Faktor O(m). Die Ausgabe ist die Anzahl <strong>der</strong> Unterschiede, die <strong>in</strong> den Routen<br />

entstehen, wenn die Kante (Verb<strong>in</strong>dung) ausfällt. Die Anfrage ist sehr ähnlich<br />

zu List<strong>in</strong>g 5.4. Nur war dort ke<strong>in</strong>e Verb<strong>in</strong>dung zwischen beiden Knoten notwendig.<br />

Hier wird nur die direkte Verb<strong>in</strong>dung zwischen beiden angegebenen<br />

Knoten gestrichen, sofern sie existiert.<br />

Zunächst wurde wie<strong>der</strong> e<strong>in</strong>e neue Variante <strong>der</strong> shortestPaths-Funktion erstellt,<br />

die genau e<strong>in</strong>e Kante ignorieren kann. Die Funktion heißt shortestpaths_edge<br />

und hat nur <strong>in</strong> Zeile 12 e<strong>in</strong>e angepasste Bed<strong>in</strong>gung <strong>in</strong> <strong>der</strong> WHERE-<br />

Klausel. Ansonsten funktioniert shortestpaths_edge genau wie shortestpaths_skip2<br />

aus List<strong>in</strong>g 5.4.<br />

Auch hier wird wie<strong>der</strong> e<strong>in</strong>e Unterschiedsfunktion erstellt. Sie heißt unterschied_edge<br />

(beg<strong>in</strong>nend <strong>in</strong> Zeile 18) und berechnet das Maß für den nachfolgend<br />

def<strong>in</strong>ierten Unterschied. Sei D E die Ausgabemenge des Algorithmus von<br />

Dijkstra auf e<strong>in</strong>em Graphen mit <strong>der</strong> Kantenmenge E und <strong>der</strong> Knotenmenge<br />

V . Dann erzeugt die Kante (u, w) ∈ E (wobei u, w ∈ V ) e<strong>in</strong>en Unterschied<br />

von<br />

|(D E \ D E\{(u,w),(w,u)} ) ∪ (D E\{(u,w),(w,u)} \ D E )|<br />

wenn sie ausfällt.<br />

© Andreas Redmer — 29. September 2011 85


5. Testläufe - Beispiele für Datenabfragen<br />

Die erste <strong>in</strong>nere Abfrage (s1) selektiert alle Timestamps und Kanten (ohne<br />

Duplikate), die nach dem 11.03.2011 (Zeile 33), zu e<strong>in</strong>er vollen Stunde (Zeile<br />

34), zwischen 15 und 18 Uhr (Zeile 36) und an e<strong>in</strong>em Wochenende (Zeile 35)<br />

aufgezeichnet wurden. Dabei werden 10074 Timestamps ausgewählt. In Zeile<br />

30 wird dann für jeden Timestamp und den zugehörigen Kanten e<strong>in</strong>mal die<br />

Funktion unterschied_edge aufgerufen. Die äußere Abfrage summiert alle<br />

ermittelten Unterschiede pro Kante auf und sortiert die höchsten Summen<br />

nach oben. Die Laufzeit kann mit<br />

abgeschätzt werden.<br />

10074 · 4 · 9ms = 362664ms ≈ 6m<strong>in</strong><br />

Fazit: Basierend auf den nach dem 11.03.2011, zu e<strong>in</strong>er vollen Stunde, zwischen<br />

15 und 18 Uhr und an e<strong>in</strong>em Wochenende aufgezeichneten Daten, würde<br />

die Verb<strong>in</strong>dung zwischen 192.168.1.93 und 192.168.1.111 die meisten Routen<br />

verän<strong>der</strong>n, wenn sie ausfallen würde. In Abbildung 5.3 werden die zehn<br />

Kanten, die hier als wichtig ermittelt wurden, grafisch dargestellt. Das Präfix<br />

192.168.“ wurde dabei aus Platzgründen weggelassen.<br />

”<br />

✞ ☎<br />

✝1.214<br />

✆<br />

✞ ☎<br />

✝ 2.7 ✆<br />

✞ ☎<br />

✝2.3<br />

✆<br />

✞ ☎ ✞ ☎ ✞ ☎ ✞ ☎<br />

✝1.112 ✆ ✝1.197<br />

✆ ✝1.166<br />

✆ ✝1.187<br />

✆<br />

✞ ☎<br />

✝ 1.184 ✆<br />

✞ ☎<br />

✝1.182<br />

✆<br />

✞ ☎<br />

✝ 1.111 ✆<br />

✞ ☎<br />

✝1.93<br />

✆<br />

✞ ☎<br />

✝1.155<br />

✆<br />

✞ ☎<br />

✝ 1.180 ✆<br />

✞ ☎<br />

✝1.69<br />

✆<br />

Abbildung 5.3.: Graph aller Kanten aus Tabelle 5.5<br />

© Andreas Redmer — 29. September 2011 86


5. Testläufe - Beispiele für Datenabfragen<br />

5.8. Suche nach Flaschenhälsen<br />

1 SELECT s4.nodea, s4.nodeb, count(s4.nodea) as w, s4.avg as alq<br />

2 FROM<br />

3 (<br />

4 select s1.time, s2.nodea,<br />

5 path (s2.nodea,array_agg(host(node) ||’,’|| host(pred)))<br />

6 from<br />

7 (<br />

8 select t.time,(shortestpaths(t.time)).* from times t<br />

9 where extract (m<strong>in</strong>ute from time)=0<br />

10 AND extract (hour from time) between 14 AND 22<br />

11 AND extract (DOW from time) IN (6,0)<br />

12 AND time >=’2011-01-01 00:00:00’<br />

13 ) as s1,<br />

14 (<br />

15 select dist<strong>in</strong>ct nodea from l<strong>in</strong>ks<br />

16 where extract (m<strong>in</strong>ute from time)=0<br />

17 AND extract (hour from time) between 14 AND 22<br />

18 AND extract (DOW from time) IN (6,0)<br />

19 AND time >=’2011-01-01 00:00:00’<br />

20 ) as s2<br />

21 GROUP BY s1.time, s2.nodea<br />

22 ) as s3,<br />

23 (<br />

24 select nodea, nodeb ,AVG((lq+lqn)/2) from l<strong>in</strong>ks<br />

25 where extract (m<strong>in</strong>ute from time)=0<br />

26 AND extract (hour from time) between 14 AND 22<br />

27 AND extract (DOW from time) IN (6,0)<br />

28 AND time >=’2011-01-01 00:00:00’<br />

29 GROUP BY nodea,nodeb<br />

30 )as s4<br />

31 where POSITION ((host(s4.nodea)||’,’||host(s4.nodeb))<br />

32 IN (host(s3.nodea) ||’,’||array_to_str<strong>in</strong>g(s3.path,’,’))) >0<br />

33 GROUP BY s4.nodea,s4.nodeb,alq<br />

34 ORDER BY w DESC LIMIT 10;<br />

List<strong>in</strong>g 5.8: Abfrage <strong>der</strong> Kanten die häufig auf <strong>der</strong> Route e<strong>in</strong>es Knotens liegen<br />

© Andreas Redmer — 29. September 2011 87


5. Testläufe - Beispiele für Datenabfragen<br />

Ausgabe: ist <strong>in</strong> Tabelle 5.6 dargestellt<br />

nodea nodeb w alq<br />

192.168.2.3 192.168.2.7 7002 0.996162393663684<br />

192.168.1.187 192.168.2.3 4008 0.999786956932234<br />

192.168.1.155 192.168.1.197 3922 0.964632478279945<br />

192.168.1.180 192.168.1.187 3377 0.968367520560566<br />

192.168.1.69 192.168.1.180 3143 0.999843477166217<br />

192.168.1.143 192.168.1.166 2469 0.972499997952046<br />

192.168.1.208 192.168.1.214 1364 0.674356127966125<br />

192.168.1.126 192.168.2.3 1289 1<br />

192.168.1.140 192.168.1.166 978 0.896254852848146<br />

192.168.2.5 192.168.2.8 914 0.998369659114088<br />

Tabelle 5.6.: Das Ergebnis <strong>der</strong> SQL-Abfrage <strong>in</strong> List<strong>in</strong>g 5.8<br />

Laufzeit: 608 Sekunden ( ≈ 10 M<strong>in</strong>uten)<br />

Erklärung: In List<strong>in</strong>g 5.8 wird versucht Flaschenhälse (also Engstellen) auf<br />

den Routen zu f<strong>in</strong>den. Dies fällt <strong>in</strong> die Problemklasse D. Dabei wird e<strong>in</strong>erseits<br />

e<strong>in</strong> Maß für die Wichtigkeit von Kanten berechnet (Spalte w <strong>in</strong> <strong>der</strong> Ausgabe),<br />

<strong>in</strong>dem geprüft wird, <strong>in</strong> wie vielen Routen die Kante vorkommt. Dies geschieht<br />

analog zu List<strong>in</strong>g 5.5. Die dort bereits def<strong>in</strong>ierte Funktion path wird auch hier<br />

verwendet. An<strong>der</strong>erseits wird auch die <strong>durch</strong>schnittliche Qualität <strong>der</strong> Verb<strong>in</strong>dung<br />

(Spalte alq <strong>in</strong> <strong>der</strong> Ausgabe) gegenübergestellt. Die Suche nach möglichen<br />

Flaschenhälsen erfolgt im Anschluss manuell, da im Rahmen dieser Arbeit ke<strong>in</strong><br />

gutes Maß für die Eigenschaft gefunden wurde, dass e<strong>in</strong> Knoten wahrsche<strong>in</strong>lich<br />

e<strong>in</strong>e kritische Engstelle ist. E<strong>in</strong>e solche Eigenschaft berechnet sich aus:<br />

w · f()<br />

alq<br />

wobei f() e<strong>in</strong>e unbekannte Funktion ist. Diese könnte konstant se<strong>in</strong>, kann aber<br />

ebenso von an<strong>der</strong>en Faktoren (wie w, alq o<strong>der</strong> <strong>der</strong> Anzahl <strong>der</strong> Knoten <strong>in</strong> <strong>der</strong><br />

Ergebnismenge) abhängig se<strong>in</strong>. Wichtig ist dabei, dass Verb<strong>in</strong>dungen mit hoher<br />

Wichtigkeit auch e<strong>in</strong>e hohe Qualität haben sollten.<br />

Im Abschnitt 1.4 wurde zum F<strong>in</strong>den von Flaschenhälsen e<strong>in</strong> an<strong>der</strong>es Verfahren<br />

beschrieben. Dabei wurde jeweils die Kante mit <strong>der</strong> ger<strong>in</strong>gsten Qualität auf<br />

e<strong>in</strong>er bestehenden Route gesucht. Das wird hier nicht getan. List<strong>in</strong>g 5.8 ignoriert<br />

den konkreten Zusammenhang zwischen Wichtigkeit und Qualität e<strong>in</strong>er<br />

Kante zu e<strong>in</strong>em bestimmten Zeitpunkt. Vielmehr wird <strong>der</strong> Durchschnittswert<br />

über mehrere Zeitpunkte ermittelt. Demnach könnte e<strong>in</strong>e Kante, die bei starker<br />

Nutzung e<strong>in</strong>e hohe Qualität hat und bei sehr ger<strong>in</strong>ger Qualität auch nicht<br />

© Andreas Redmer — 29. September 2011 88


5. Testläufe - Beispiele für Datenabfragen<br />

genutzt wird (was eigentlich <strong>in</strong> Ordnung ist), <strong>durch</strong> die Durchschnittsbildung,<br />

schon als Problemstelle erkannt werden.<br />

Die Abfrage s1 wählt zunächst alle Timestamps aus, die nach dem 01.01.2011<br />

(Zeile 12), an e<strong>in</strong>em Wochenende (Zeile 11), zu e<strong>in</strong>er vollen Stunde (Zeile 9)<br />

und zwischen 14 Uhr und 22 Uhr (Zeile 10) aufgezeichnet wurden. Gleichzeitig<br />

werden alle geltenden Routen für diese Zeitpunkte berechnet. Die Abfrage s2<br />

wählt alle Knoten (ohne Duplikate) aus, die <strong>in</strong> diesem Zeitraum vorhanden<br />

waren. In s3 wird das Kreuzprodukt aus s1 und s2 erstellt. Dabei wird für<br />

jedes erhaltene Paar (Timestamp, Knoten) <strong>der</strong> Pfad <strong>in</strong>s Internet berechnet. In<br />

s4 werden alle Kanten (ohne Duplikate), die <strong>in</strong> diesem Zeitraum existierten<br />

und ihre <strong>durch</strong>schnittliche Qualität ermittelt. Zum Schluss werden die Ergebnisse<br />

von s3 und s4 mit e<strong>in</strong>em Inner Jo<strong>in</strong> verbunden und zwar immer genau<br />

dann, wenn die Kante aus s4 <strong>in</strong> dem Pfad aus s3 vorkommt. In diesem Fall<br />

wird auch <strong>der</strong> Anfangsknoten selbst als Teil des Pfades betrachtet (was <strong>in</strong> List<strong>in</strong>g<br />

5.5 nicht nötig war). Dafür wird nicht <strong>der</strong> conta<strong>in</strong>s-Operator für Arrays<br />

verwendet, son<strong>der</strong>n e<strong>in</strong> Vergleich zweier Zeichenketten <strong>durch</strong>geführt (Zeile 31<br />

und 32), da die beiden Elemente genau nache<strong>in</strong>an<strong>der</strong> auf <strong>der</strong> Route stehen<br />

müssen. Am Ende wird die Ausgabe nach Wichtigkeit <strong>der</strong> Kante sortiert, während<br />

die <strong>durch</strong>schnittliche Qualität nur als zusätzliche Information ausgegeben<br />

wird. Mit dieser zusätzlichen Information ist es möglich, von den wichtigsten<br />

Kanten, die möglichen Problemstellen zu f<strong>in</strong>den.<br />

Fazit: Gemessen an den Daten, die seit dem 01.01.2011, am Wochenende,<br />

zwischen 14 und 22 Uhr zu e<strong>in</strong>er vollen Stunde aufgezeichnet wurden. Ist die<br />

Verb<strong>in</strong>dung zwischen 192.168.2.3 und 192.168.2.7 mit Abstand die Wichtigste.<br />

Die Verb<strong>in</strong>dung zwischen 192.168.1.208 und 192.168.1.214 liegt auf Platz<br />

7 und ist damit ebenfalls sehr wichtig. Sie hat allerd<strong>in</strong>gs nur e<strong>in</strong>e <strong>durch</strong>schnittliche<br />

Qualität von 0, 67 (das heißt nur etwa zwei Drittel aller Pakete kommen<br />

an). Diese Verb<strong>in</strong>dung ist (<strong>in</strong> diesen Nutzungszeiten) höchstwahrsche<strong>in</strong>lich e<strong>in</strong>e<br />

Problemstelle. Es sollte untersucht werden, welche Art von Verb<strong>in</strong>dung dort<br />

besteht und ob sie <strong>durch</strong> geeignete Maßnahmen verbessert werden kann.<br />

© Andreas Redmer — 29. September 2011 89


6. Zusammenfassung und Ausblick<br />

6. Zusammenfassung und Ausblick<br />

6.1. Zusammenfassung<br />

In dieser Arbeit wurde e<strong>in</strong> Konzept vorgestellt, dass die Performance <strong>der</strong> <strong>Datenanalyse</strong><br />

von <strong>Netzwerkgraphen</strong> unter Verwendung von UDF stark beschleunigt.<br />

Die Berechnung <strong>der</strong> gültigen Routen für e<strong>in</strong>en Zeitpunkt dauert nun<br />

nur noch neun Millisekunden, statt vorher vier Sekunden. Weiterh<strong>in</strong> wurde die<br />

Usability da<strong>durch</strong> so verän<strong>der</strong>t, dass e<strong>in</strong>e <strong>Datenanalyse</strong> nun mit Hilfe von SQL<br />

erfolgen kann. Dabei s<strong>in</strong>d Abfragen aus allen vorher def<strong>in</strong>ierten Problemklassen<br />

möglich. Zusätzlich kann die <strong>Datenanalyse</strong> <strong>in</strong>-place erfolgen, ohne dass die<br />

Daten zu e<strong>in</strong>em schnelleren Computer (bzw. <strong>in</strong> e<strong>in</strong>e Cloud) transportiert werden<br />

müssen. Es stellte sich heraus, dass PL/Java-UDF auf dem PostgreSQL-<br />

Server die beste Usability und Performance bieten um die <strong>Datenanalyse</strong> direkt<br />

auf dem SQL-Server stattf<strong>in</strong>den zu lassen. Die e<strong>in</strong>zige nötige Umstellung ist<br />

<strong>der</strong> Wechsel des DBMS von MySQL auf PostgreSQL o<strong>der</strong> zum<strong>in</strong>dest e<strong>in</strong>e zusätzliche<br />

Installation e<strong>in</strong>es PostgreSQL-Servers. Das Programm, dass die Datenaufzeichnung<br />

vornimmt, kann <strong>durch</strong> e<strong>in</strong>e m<strong>in</strong>imale Än<strong>der</strong>ung auf den neuen<br />

Server umgestellt werden.<br />

Zunächst wurde jedoch die Komplexität <strong>der</strong> Implementierung von Mundt<br />

und Vetterick [22] untersucht. Dabei wurde festgestellt, dass diese <strong>in</strong> e<strong>in</strong>er zu<br />

hohen Komplexitätsklasse liegt und algorithmisch optimiert werden müsste.<br />

Die Implementierung des Dijkstra-Algorithmus, <strong>in</strong> e<strong>in</strong>er besseren Komplexitätsklasse,<br />

beschleunigte ihn sehr stark. Es wurden e<strong>in</strong>ige weitere algorithmische<br />

<strong>Performanceoptimierung</strong>en vorgenommen. Der <strong>in</strong> <strong>der</strong> Praxis bewährte<br />

und sehr übliche Fibonacci-Heap beschleunigt auch <strong>in</strong> diesem Fall die Ausführung.<br />

Außerdem wurde mit <strong>der</strong> General-Gateway-Strategie e<strong>in</strong>e Vorgehensweise<br />

entdeckt, die dafür sorgt, dass die kürzesten Wege jetzt nur noch e<strong>in</strong>mal pro<br />

Graph und nicht mehrmals pro Graph (für je e<strong>in</strong> Gateway) ausgeführt werden<br />

muss. Weitere Optimierungen des Dijkstra-Algorithmus, die ihn beschleunigen,<br />

<strong>in</strong>dem sie ihn früher abbrechen lassen, konnten im Rahmen dieser Arbeit<br />

aufgrund <strong>der</strong> Problemstellung nicht e<strong>in</strong>gesetzt werden.<br />

E<strong>in</strong>en optimierten Programmierstil zu verwenden, hat nur ger<strong>in</strong>gfügige Verbesserungen<br />

mit sich gebracht. Das lag <strong>in</strong>sbeson<strong>der</strong>e daran, dass die mo<strong>der</strong>nen<br />

Compiler schon sehr gut optimieren, und dass die Lesbarkeit und Erweiterbar-<br />

© Andreas Redmer — 29. September 2011 90


6. Zusammenfassung und Ausblick<br />

keit <strong>der</strong> UDF nicht unermesslich verschlechtert werden sollten. Wichtig ist es,<br />

bei <strong>der</strong> Implementierung e<strong>in</strong>en konstanten (sehr hohen) Wert für ∞ und die<br />

Adjazenzmatrix statt e<strong>in</strong>er Liste zu verwenden. Durch die sehr ger<strong>in</strong>ge Größe,<br />

<strong>der</strong> hier verwendeten Graphen, brauchte die Optimierung des Speicherplatzes<br />

nicht beachtet zu werden.<br />

Im Rahmen dieser Arbeit ist es nicht gelungen, <strong>durch</strong> Parallelisierung mehrerer<br />

Dijkstra-Berechnungen noch weitere Beschleunigungen zu erreichen. Zwar<br />

können mehrere Abfragen auf e<strong>in</strong>em Server mit Multi-Core-CPU gleichzeitig<br />

ausgeführt werden, jedoch muss dies immer manuell geschehen. Der Versuch<br />

e<strong>in</strong>e Multi-Core-Schnittstelle per UDF bereit zu stellen, endete dar<strong>in</strong>, dass die<br />

Usability zu sehr e<strong>in</strong>geschränkt war. Damit konnte nur sehr aufwändig und<br />

auch nur e<strong>in</strong>e bestimmte Problemklasse angefragt werden. Mehrere Dijkstra-<br />

Berechnungen müssen also weiterh<strong>in</strong> parallel, von mehreren Clients an den<br />

Server gesendet werden. E<strong>in</strong>e Parallelisierung des Dijkstra-Algorithmus selbst<br />

(auf CPU und GPU) brachte ke<strong>in</strong>en Geschw<strong>in</strong>digkeitsvorteil. Das Hauptproblem<br />

war dabei jeweils, dass die Graphen mit ca. 200 Knoten viel zu kle<strong>in</strong> s<strong>in</strong>d<br />

um sie <strong>durch</strong> parallele Verarbeitung zu beschleunigen. Diese Möglichkeiten s<strong>in</strong>d<br />

also zukünftig nur für sehr große Graphen <strong>in</strong>teressant.<br />

Durch e<strong>in</strong>e Reihe beispielhafter SQL-Anfragen wurde die Verwendung <strong>der</strong><br />

erstellten UDF demonstriert und die E<strong>in</strong>satzgebiete aufgezeigt. Für die spätere<br />

Verwendung <strong>durch</strong> e<strong>in</strong>en Datenanalysten s<strong>in</strong>d die Spezifikation <strong>der</strong> UDF<br />

(Abschnitt 3.2) und die Beispielanfragen im Kapitel 5 gute E<strong>in</strong>stiegspunkte.<br />

Die vorgestellten SQL-Abfragen weisen teilweise sehr hohe Laufzeiten auf, so<br />

dass auch jeweils Methoden zur vorherigen Abschätzung des Zeitaufwandes<br />

angegeben wurden. Dabei wird nochmal sehr e<strong>in</strong>drucksvoll deutlich, wie stark<br />

sich die Verbesserung von vier Sekunden auf neun Millisekunden auf die späteren<br />

<strong>Datenanalyse</strong>n auswirkt. Komplexe <strong>Datenanalyse</strong>n waren vorher nicht<br />

effizient möglich. Die gezeigten <strong>Datenanalyse</strong>n konnten auch vorher schon gemacht<br />

werden, jedoch we<strong>der</strong> schnell noch komfortabel. Insbeson<strong>der</strong>e bietet die<br />

Datenbank <strong>der</strong> Google-Cloud e<strong>in</strong>e so e<strong>in</strong>geschränkte Anfragesprache, dass die<br />

Analyse fast immer <strong>in</strong> e<strong>in</strong>em geson<strong>der</strong>ten Programm nach <strong>der</strong> Abfrage <strong>der</strong><br />

Daten passieren muss.<br />

6.2. Ausblick<br />

Weitere Optimierung<br />

Für die Topology-Control-Daten des Opennet-Netzwerkes ist e<strong>in</strong>e weitere signifikante<br />

Beschleunigung des Dijkstra-Algorithmus <strong>in</strong> Zukunft unwahrsche<strong>in</strong>lich.<br />

© Andreas Redmer — 29. September 2011 91


6. Zusammenfassung und Ausblick<br />

Jedoch nimmt <strong>der</strong> Dijkstra weniger als 12% <strong>der</strong> gesamten Laufzeit <strong>der</strong> UDF<br />

<strong>in</strong> Anspruch. Der Transport <strong>der</strong> Daten zur und aus <strong>der</strong> UDF dauert relativ<br />

lange. Wobei nicht nicht klar ist, ob <strong>der</strong> Datenbankserver und die Selektion<br />

<strong>der</strong> Daten vor <strong>der</strong> UDF dabei zu langsam ist o<strong>der</strong> die Aufbereitung <strong>der</strong> Daten<br />

nach <strong>der</strong> UDF-Ausführung o<strong>der</strong> die Kommunikation zwischen PostgreSQL-<br />

SPI und <strong>der</strong> Java-VM so langsam ist. Die PL/Java-Schnittstelle zum SPI ist<br />

ke<strong>in</strong>esfalls <strong>in</strong>performant programmiert. Dennoch wäre es <strong>in</strong>teressant, ob die<br />

Ausführung von kompilierten UDF <strong>in</strong> <strong>der</strong> Programmiersprache C die Ausführung<br />

beschleunigen. Weiterh<strong>in</strong> könnte auch e<strong>in</strong> kommerzielles DBMS (wie DB2<br />

o<strong>der</strong> Oracle) testweise für solche UDF verwendet werden. Eventuell lassen sich<br />

da<strong>durch</strong> noch wesentlich bessere Ergebnisse erzielen. Dazu s<strong>in</strong>d künftig noch<br />

weitere Untersuchungen nötig.<br />

Wenn das hier vorgestellte Konzept für sehr große Graphen e<strong>in</strong>gesetzt werden<br />

soll, kann <strong>durch</strong>aus auf die vorgestellten Möglichkeiten zur Parallelisierung<br />

zurückgegriffen werden. Auch die Verwendung <strong>der</strong> GPU auf <strong>der</strong> Grafikkarte ist<br />

technisch <strong>in</strong> PL/Java-UDF möglich. Da<strong>durch</strong>, dass <strong>in</strong>sbeson<strong>der</strong>e <strong>der</strong> Transport<br />

<strong>in</strong> den Speicher <strong>der</strong> Grafikkarte sehr langsam war, ist es <strong>durch</strong>aus denkbar, dass<br />

die GPU auch Vorteile br<strong>in</strong>gt, wenn wesentlich komplexere Algorithmen berechnet<br />

werden sollen. Beispielsweise die Berechnung m<strong>in</strong>imaler Spannbäume<br />

o<strong>der</strong> die Lösung des Cliquenproblems könnte auf <strong>der</strong> GPU <strong>durch</strong>aus schneller<br />

se<strong>in</strong>. Auch die <strong>in</strong> dieser Arbeit vorgestellten Algorithmen, die nur e<strong>in</strong>en<br />

Knoten o<strong>der</strong> e<strong>in</strong>e Kante aus dem Graphen streichen und dann den Dijkstra-<br />

Algorithmus erneut berechnen, könnten <strong>in</strong> dedizierte PL/Java-UDF (mit o<strong>der</strong><br />

ohne GPU-Support) programmiert werden, um e<strong>in</strong>e weitere Beschleunigung zu<br />

erreichen. Inwiefern dies <strong>in</strong> Zukunft s<strong>in</strong>nvoll ist, hängt davon ab, wie oft e<strong>in</strong><br />

bestimmtes Problem aus e<strong>in</strong>er bestimmten Problemklasse gelöst werden muss.<br />

In jedem Fall liefert diese Arbeit alle Grundlagen für <strong>der</strong>artige Erweiterungen.<br />

An dieser Stelle sei noch bemerkt, dass die <strong>in</strong> Kapitel 5 vorgestellten Analysen<br />

selbst auch noch nicht performanceoptimiert erstellt s<strong>in</strong>d. Die dortigen<br />

Abfragen und die neuen Funktionen (z. B. unterschied o<strong>der</strong> path) können<br />

noch beschleunigt werden. Sie s<strong>in</strong>d dort nur sehr e<strong>in</strong>fach und <strong>in</strong> kurzer Schreibweise<br />

dargestellt. Weitere Beschleunigungen s<strong>in</strong>d auch <strong>durch</strong> die Verwendung<br />

<strong>der</strong> Mechanismen im DBMS wie z. B. Indizes, gespeicherte Anfagepläne o<strong>der</strong><br />

zwischengespeicherte Teilergebnisse (Materialisierung) möglich.<br />

Sollte die Berechnung <strong>in</strong> <strong>der</strong> Cloud trotz ihrer Nachteile zukünftig weiterh<strong>in</strong><br />

e<strong>in</strong>e Alternative darstellen, so bietet diese Arbeit e<strong>in</strong>e gute E<strong>in</strong>führung <strong>in</strong> e<strong>in</strong>e<br />

effiziente Dijkstra-Implementierung. Die hier vorgenommenen algorithmischen<br />

Verbesserungen (<strong>in</strong>sbeson<strong>der</strong>e die General-Gateway-Strategie) sollte auch zukünftig<br />

übernommen werden.<br />

© Andreas Redmer — 29. September 2011 92


6. Zusammenfassung und Ausblick<br />

<strong>Datenanalyse</strong><br />

Die SQL-Abfragen die <strong>in</strong> Kapitel 5 vorgestellt wurden, zeigen das große Potential<br />

auf, für das die erschaffenen UDF künftig e<strong>in</strong>gesetzt werden können.<br />

Dabei wurden je drei verschiedene Maße vorgestellt, mit denen zukünftig die<br />

Wichtigkeit <strong>der</strong> Knoten und Kanten festgestellt werden kann. Mit den Abfragen<br />

aus <strong>der</strong> Problemklasse B und C und entsprechend neuen Erkenntnissen ist<br />

unter Umständen e<strong>in</strong>e Verbesserung des Rout<strong>in</strong>g-Protokolls und e<strong>in</strong>e bessere<br />

Netzplanung möglich.<br />

In dieser Arbeit wurden auch zwei Möglichkeiten beschrieben, um mögliche<br />

Flaschenhälse im Netzwerk zu bestimmen. Dies fällt <strong>in</strong> die Problemklasse D.<br />

Dazu wird auf die Ergebnisse <strong>der</strong> Problemklasse B aufgebaut. Da<strong>durch</strong> können<br />

zukünftig Schwachstellen im Netzwerk gefunden und verbessert werden.<br />

Für die Problemklasse E (das F<strong>in</strong>den von Alternativrouten) wurde <strong>in</strong> dieser<br />

Arbeit ke<strong>in</strong> Beispiel angegeben. Der Algorithmus von Yen (vgl. [33]) wurde<br />

nicht <strong>in</strong> die PL/Java-UDF e<strong>in</strong>gebaut um die Usability nicht zu bee<strong>in</strong>trächtigen.<br />

Es ist jedoch möglich, mit den vorgestellten SQL-Anfragen, e<strong>in</strong>zelne Verb<strong>in</strong>dungen<br />

(auf dem ”<br />

besten“ Pfad) aus dem Graphen zu streichen und dann den<br />

Dijkstra erneut auszuführen und die entstehenden Alternativrouten zu prüfen.<br />

Auch dafür kann auf die Ergebnisse <strong>der</strong> Problemklasse B aufgebaut werden.<br />

Das ist auch das, was <strong>der</strong> Algorithmus von Yen im Wesentlichen tut, jedoch <strong>in</strong><br />

e<strong>in</strong>er besseren Komplexitätsklasse (also schneller). Ob es zukünftig nötig ist,<br />

den Algorithmus von Yen <strong>in</strong> e<strong>in</strong>e geson<strong>der</strong>te PL/Java-UDF zu implementieren,<br />

hängt davon ab, wie stark die Nachfrage nach solchen Ergebnissen ist.<br />

Auch weitere noch nicht genannte Problemklassen lassen sich nach Bedarf<br />

<strong>in</strong> neue PL/Java-UDF implementieren, wenn sich diese zukünftig als relevant<br />

ergeben sollten. Die Möglichkeit die Daten aus <strong>der</strong> Datenbank <strong>in</strong> e<strong>in</strong>e<br />

Java-Klasse zu importieren, ist bereits geschaffen. Es muss also nur noch <strong>der</strong><br />

Algorithmus selbst <strong>in</strong>tegriert werden, wobei <strong>durch</strong>aus e<strong>in</strong>e bestehende Java-<br />

Implementierung genutzt werden kann. Die Ausgabe des Ergebnisses unterscheidet<br />

sich meist von <strong>der</strong> beim Dijkstra. Demnach muss für neue Probleme<br />

e<strong>in</strong>e neue Rückgabefunktion geschaffen werden und die Funktionen müssen<br />

auch <strong>in</strong> e<strong>in</strong>em an<strong>der</strong>en SQL-Kontext aufgerufen werden. Dies gilt z. B. für das<br />

F<strong>in</strong>den m<strong>in</strong>imaler Spannbäume, k-Cliquen, Zyklen, M<strong>in</strong>oren, Stabilitätszahlen,<br />

Paarungen, Flüssen, Schnitten, Hamiltonkreisen und vielen mehr. E<strong>in</strong>ige neue<br />

Problemstellungen können auch mit den bestehenden Funktionen und dem<br />

Dijkstra-Algorithmus gelöst werden. Beispielsweise kann e<strong>in</strong>e Partitionierung<br />

des Graphen mit den bestehenden Funktionen gefunden werden. E<strong>in</strong> Verfahren<br />

dazu ist im Anhang B vorgestellt.<br />

© Andreas Redmer — 29. September 2011 93


Literaturverzeichnis<br />

Literaturverzeichnis<br />

1 ApacheSoftwareFoundation: The Apache Cassandra Project Webseite.<br />

2011. – URL http://cassandra.apache.org/. – Zugriffsdatum:<br />

07.08.2011<br />

2 Badis, Hakim ; Al Agha, Khaldoun: QOLSR, QoS rout<strong>in</strong>g for ad hoc<br />

wireless networks us<strong>in</strong>g OLSR. In: European Transactions on Telecommunications<br />

16 (2005), Nr. 5, S. 427–442. – URL http://dx.doi.org/10.<br />

1002/ett.1067. – ISSN 1541-8251<br />

3 Bellman, R. E.: On a Rout<strong>in</strong>g Problem. In: Quarterly of Applied Mathematics<br />

16 (1958), Nr. 1, S. 87–90<br />

4 Brodal, Gerth S. ; Träff, Jesper L. ; Zaroliagis, Christos D.: A<br />

Parallel Priority Queue with Constant Time Operations. In: Journal of<br />

Parallel and Distributed Comput<strong>in</strong>g 49 (1998), S. 4–21<br />

5 Chang, Fay ; Dean, Jeffrey ; Ghemawat, Sanjay ; Hsieh, Wilson C. ;<br />

Wallach, Deborah A. ; Burrows, Mike ; Chandra, Tushar ; Fikes,<br />

Andrew ; Gruber, Robert E.: Bigtable: A Distributed Storage System for<br />

Structured Data. In: ACM Trans. Comput. Syst. 26 (2008), June, S. 4:1–<br />

4:26. – URL http://doi.acm.org/10.1145/1365815.1365816. – ISSN<br />

0734-2071<br />

6 Clausen, Thomas ; Jacquet, Philippe ; Adjih, Cédric ; Laouiti, Anis ;<br />

M<strong>in</strong>et, Pascale ; Muhlethaler, Paul ; Qayyum, Amir ; Viennot, Laurent:<br />

Optimized L<strong>in</strong>k State Rout<strong>in</strong>g Protocol (OLSR). In: Network Work<strong>in</strong>g<br />

Group Request for Comments : 3626 Category : Experimental (2003)<br />

7 Couto, Douglas S. J. D. ; Aguayo, Daniel ; Bicket, John ; Morris,<br />

Robert: a high-throughput path metric for multi-hop wireless rout<strong>in</strong>g.<br />

In: Wireless Networks 11 (2005), S. 419–434. – URL http://dx.doi.<br />

org/10.1007/s11276-005-1766-z. – 10.1007/s11276-005-1766-z. – ISSN<br />

1022-0038<br />

8 Dijkstra, Edsger W.: A note on two problems <strong>in</strong> connexion with graphs.<br />

In: Numerische Mathematik 1 (1959), S. 269–271<br />

9 D<strong>in</strong>ic, E. A.: Algorithm for solution of a problem of maximum flow <strong>in</strong> a<br />

network with power estimaton. (1970), S. 1277–1280<br />

© Andreas Redmer — 29. September 2011 94


Literaturverzeichnis<br />

10 Driscoll, J.R. ; Gabow, H.N. ; Shrairman, R. ; Tarjan, R.E.: Relaxed<br />

heaps: An alternative to Fibonacci heaps with applications to parallel<br />

computation. In: Assoc. Comput. (1988), 03, Nr. 11, S. 1343–1354<br />

11 Edmonds, J. ; Karp, Richard M.: Theoretical Improvements <strong>in</strong> Algorithmic<br />

Efficiency for Network Flow Problems. In: Journal of ACM 18 (1972),<br />

S. 248–264<br />

12 Ellis, Jonathan: Cassandra Summit - The State of Cassandra. Video.<br />

12. August 2010. – URL http://riptano.blip.tv/file/4011925/. –<br />

Zugriffsdatum: 18.08.2011<br />

13 Floyd, Robert W.: Algorithm 97 (SHORTEST PATH). In: Communications<br />

of the ACM 5 (1962), Nr. 6, S. 345<br />

14 Forbrig, Peter: Introduction to Programm<strong>in</strong>g by Abstract Data Types.<br />

Leipzig : Fachbuchverlag, 2001. – ISBN 3-446-21782-7<br />

15 Ford, Lester R. ; Fulkerson, Delbert R.: Maximal flow through a network.<br />

In: Canadadian Journal of Mathematics 8 (1956), S. 399–404<br />

16 Fredman, Michael L. ; Tarjan, Robert E.: Fibonacci heaps and their<br />

uses <strong>in</strong> improved network optimization algorithms. In: J. ACM 34 (1987),<br />

July, S. 596–615. – URL http://doi.acm.org/10.1145/28869.28874. –<br />

ISSN 0004-5411<br />

17 Harish, Pawan ; Narayanan, P. J.: Accelerat<strong>in</strong>g large graph algorithms<br />

on the GPU us<strong>in</strong>g CUDA. In: IEEE High Performance Comput<strong>in</strong>g (2007),<br />

S. 197–208<br />

18 Hart, P. E. ; Nilsson, N. J. ; Raphael, B.: A Formal Basis for the<br />

Heuristic Determ<strong>in</strong>ation of M<strong>in</strong>imum Cost Paths. In: IEEE Transactions<br />

on Systems Science and Cybernetics SSC 4 (1968), Nr. 2, S. 100–107<br />

19 Hutter, Marco: JUDA - Java b<strong>in</strong>d<strong>in</strong>gs for CUDA. 2011. – URL http:<br />

//www.jcuda.org/. – Zugriffsdatum: 14.08.2011<br />

20 ItNovum: Whitepaper: Open Source-Datenbanken. 2011. - URL<br />

http://www.it-novum.com/download/downloads/whitepaper-opensource-datenbanken.html<br />

- Zugriffsdatum 15.08.2011<br />

21 JUnit: JUnit Webseite. 2011. – URL http://www.junit.org/. – Zugriffsdatum:<br />

28.05.2011<br />

22 Mundt, Thomas ; Vetterick, Jonas: Network Topology Analysis <strong>in</strong> the<br />

Cloud. In: ICOMP’11 - The 2011 International Conference on Internet<br />

Comput<strong>in</strong>g, July 2011<br />

23 Naveh, Barak: JGraphT - a free Java Graph Library. 2005. – URL<br />

http://www.jgrapht.org/. – Zugriffsdatum: 15.08.2011<br />

© Andreas Redmer — 29. September 2011 95


Literaturverzeichnis<br />

24 Opennet: Opennet e.V. Webseite. 2011. – URL http://wiki.<br />

opennet-<strong>in</strong>itiative.de/. – Zugriffsdatum: 05.06.2011<br />

25 Oracle: Java API Onl<strong>in</strong>edokumentation. 2011. – URL<br />

http://download.oracle.com/javase/1.4.2/docs/api/. – Zugriffsdatum:<br />

27.05.2011<br />

26 Oracle: Java API Onl<strong>in</strong>edokumentation <strong>der</strong> Klasse SYSTEM. 2011.<br />

– URL http://download.oracle.com/javase/1,5.0/docs/api/java/<br />

lang/System.html. – Zugriffsdatum: 27.05.2011<br />

27 pgAdm<strong>in</strong>: pgAdm<strong>in</strong>: PostgreSQL adm<strong>in</strong>istration and management tools.<br />

2011. – URL http://www.pgadm<strong>in</strong>.org/. – Zugriffsdatum: 21.08.2011<br />

28 RefractionsResearch: PostGIS Webseite. 2011. – URL http:<br />

//postgis.refractions.net/. – Zugriffsdatum: 21.05.2011<br />

29 Stentz, Anthony: Real-Time Replann<strong>in</strong>g <strong>in</strong> Dynamic and Unknown Environments.<br />

2000. – URL http://www.frc.ri.cmu.edu/~axs/dynamic_<br />

plan.html. – Zugriffsdatum: 27.05.2011<br />

30 Stonebraker, M. ; Rowe, L.A. ; Hirohama, M.: The implementation<br />

of POSTGRES. In: Knowledge and Data Eng<strong>in</strong>eer<strong>in</strong>g, IEEE Transactions<br />

on 2 (1990), mar, Nr. 1, S. 125 –142. – ISSN 1041-4347<br />

31 Stonebraker, Michael ; Kemnitz, Greg: The POSTGRES next generation<br />

database management system. In: Commun. ACM 34 (1991), October,<br />

S. 78–92. – URL http://doi.acm.org/10.1145/125223.125262. – ISSN<br />

0001-0782<br />

32 Straube, Georgi: Bachelorarbeit - Cloud Comput<strong>in</strong>g zur Optimierung e<strong>in</strong>er<br />

Netzwerkpartitionierung, Universität Rostock, Fakultät für Informatik<br />

und Elektrotechnik. August 2011<br />

33 Yen, J. Y.: F<strong>in</strong>d<strong>in</strong>g the k shortest loopless paths <strong>in</strong> a network. In: Management<br />

Science 17 (1971), S. 712–716<br />

© Andreas Redmer — 29. September 2011 96


A. Anhang: SQL Anfragen<br />

A. Anhang: SQL Anfragen<br />

A.1. Anzahl neuer Datensätze pro M<strong>in</strong>ute<br />

1 --count average new datasets per m<strong>in</strong>ute<br />

2 select avg(count) from (<br />

3 -- count number of datasets per m<strong>in</strong>ute<br />

4 select time,count(*) from l<strong>in</strong>ks<br />

5 where time between<br />

6 -- 3 weeks before last date<br />

7 (select max(time)from times)-<strong>in</strong>terval’3 weeks’<br />

8 and<br />

9 -- last date<br />

10 (select max(time)from times)<br />

11 group by time<br />

12 ) as s1;<br />

13 -- Ausgabe: 852.3924772162386081<br />

List<strong>in</strong>g A.1: Abfrage <strong>der</strong> Anzahl neuer Datensätze pro M<strong>in</strong>ute<br />

List<strong>in</strong>g A.1 zeigt die <strong>durch</strong>schnittliche Anzahl <strong>der</strong> Datensätze pro Timestamp<br />

<strong>in</strong> den letzten drei Wochen <strong>der</strong> Aufzeichnung. Dies ist etwa die Anzahl<br />

<strong>der</strong> Datensätze, die pro M<strong>in</strong>ute zum Datenbestand h<strong>in</strong>zu kommen.<br />

© Andreas Redmer — 29. September 2011 i


A. Anhang: SQL Anfragen<br />

A.2. Prüfung <strong>der</strong> Vollständigkeit <strong>der</strong> Daten<br />

1 -- korrekt aufgezeichnete Timestamps nach Anzahl <strong>der</strong> E<strong>in</strong>traege<br />

2 -- pro Timestamp<br />

3 select count (*) from (<br />

4 select time,count(*) c from l<strong>in</strong>ks<br />

5 group by time<br />

6 hav<strong>in</strong>g count(*) between 44 and 1600<br />

7 ) as s1<br />

8 -- Ausgabe: 334829 (korrekte)<br />

9<br />

10<br />

11 -- Timestamps pro Stunde = 60<br />

12 select count(c) FROM<br />

13 (<br />

14 select date_trunc(’hour’, time),count (*) c from times<br />

15 --skip first hour (always <strong>in</strong>complete!)<br />

16 WHERE time ><br />

17 date_trunc(’hour’, timestamp ’2010-04-07 14:20:02’+<strong>in</strong>terval ’1 hour’)<br />

18 --skip last hour (always <strong>in</strong>complete!)<br />

19 AND time <<br />

20 date_trunc(’hour’, timestamp ’2011-03-28 17:41:02’-<strong>in</strong>terval ’1 hour’)<br />

21 group by date_trunc(’hour’, time)<br />

22 hav<strong>in</strong>g count (*) = 60<br />

23 )as s1;<br />

24 -- Ausgabe: 5570 (richtige) (H<strong>in</strong>weis: alle: 5586 und falsche: 16)<br />

25<br />

26<br />

27 -- Timestamps pro Tag = 1440<br />

28 select count(c) FROM<br />

29 (<br />

30 select date_trunc(’day’, time),count (*) c from times<br />

31 --skip first day (always <strong>in</strong>complete!)<br />

32 WHERE time >= ’2010-04-08 00:00:00’<br />

33 --skip last day (always <strong>in</strong>complete!)<br />

34 AND time < ’2011-03-28 00:00:00’<br />

35 group by date_trunc(’day’, time)<br />

36 hav<strong>in</strong>g count (*) = 1440<br />

37 )as s1;<br />

38 -- Ausgabe: 223 (richtige) (H<strong>in</strong>weis: alle: 235 und falsche: 12)<br />

39<br />

40<br />

41 -- Timestamps pro Woche = 10080<br />

42 select count(c) FROM<br />

43 (<br />

44 select EXTRACT(WEEK FROM time),count (*) c from times<br />

45 --skip first week (always <strong>in</strong>complete!)<br />

46 WHERE time >= ’2010-04-12 00:00:00’<br />

47 --skip last week (always <strong>in</strong>complete!)<br />

48 AND time < ’2011-03-28 00:00:00’<br />

49 group by EXTRACT(WEEK FROM time)<br />

50 hav<strong>in</strong>g count (*) = 10080<br />

51 )as s1;<br />

52 -- Ausgabe: 25 (richtige) (H<strong>in</strong>weis: alle: 35 und falsche: 10)<br />

List<strong>in</strong>g A.2: Abfrage <strong>der</strong> Vollständigkeit <strong>der</strong> Daten<br />

© Andreas Redmer — 29. September 2011 ii


A. Anhang: SQL Anfragen<br />

1 -- (m<strong>in</strong>,max,avg) aller Luecken <strong>in</strong> den Daten<br />

2 SELECT m<strong>in</strong>(diff),max(diff),avg(diff) FROM (<br />

3 SELECT t1t,t2t,t2t-t1t diff FROM (<br />

4 SELECT date_trunc (’m<strong>in</strong>ute’,t1.time) t1t ,date_trunc (’m<strong>in</strong>ute’,t2.time)<br />

5 t2t FROM times t1, times t2<br />

6 WHERE t2.time = (SELECT MIN(time) FROM times t3 WHERE t3.time >t1.time)<br />

7 ) as s1<br />

8 where t2t-t1t > <strong>in</strong>terval’1 m<strong>in</strong>ute’<br />

9 ) as s2;<br />

10 --Ausgabe:<br />

11 --m<strong>in</strong>:"00:02:00"<br />

12 --max:"108 days 04:30:00"<br />

13 --avg:"8 days 18:05:17.142857"<br />

14<br />

15<br />

16 -- Abfrage <strong>der</strong> Luecken selbst<br />

17 SELECT date_trunc (’day’,t1t), m<strong>in</strong>(diff),max(diff),avg(diff)<br />

18 FROM (<br />

19 SELECT t1t,t2t,t2t-t1t diff FROM (<br />

20 SELECT date_trunc (’m<strong>in</strong>ute’,t1.time) t1t ,date_trunc (’m<strong>in</strong>ute’,t2.time)<br />

21 t2t FROM times t1, times t2<br />

22 WHERE t2.time =<br />

23 (SELECT MIN(t3.time) FROM times t3 WHERE t3.time >t1.time)<br />

24 ) as s1<br />

25 where t2t-t1t > <strong>in</strong>terval’1 m<strong>in</strong>ute’<br />

26 ) as s2<br />

27 GROUP BY date_trunc (’day’,t1t);<br />

28<br />

29 -- Ausgabe:<br />

30 -- "2010-04-07 14:38:00+02";"2010-04-07 14:41:00+02";"00:03:00"<br />

31 -- "2010-05-22 23:02:00+02";"2010-05-24 11:23:00+02";"1 day 12:21:00"<br />

32 -- "2010-05-24 11:35:00+02";"2010-09-09 16:05:00+02";"108 days 04:30:00"<br />

33 -- "2010-09-10 12:21:00+02";"2010-09-10 12:23:00+02";"00:02:00"<br />

34 -- "2010-09-10 12:26:00+02";"2010-09-10 13:00:00+02";"00:34:00"<br />

35 -- "2010-09-20 13:56:00+02";"2010-09-20 14:38:00+02";"00:42:00"<br />

36 -- "2010-09-21 14:42:00+02";"2010-09-21 16:33:00+02";"01:51:00"<br />

37 -- "2010-10-31 01:59:00+02";"2010-10-31 02:00:00+01";"01:01:00"<br />

38 -- "2010-11-27 13:42:00+01";"2010-12-09 14:46:00+01";"12 days 01:04:00"<br />

39 -- "2011-01-17 17:13:00+01";"2011-01-17 17:15:00+01";"00:02:00"<br />

40 -- "2011-02-09 07:24:00+01";"2011-02-09 22:19:00+01";"14:55:00"<br />

41 -- "2011-03-28 17:05:00+02";"2011-03-28 17:08:00+02";"00:03:00"<br />

42 -- "2011-03-28 17:25:00+02";"2011-03-28 17:27:00+02";"00:02:00"<br />

43 -- "2011-03-28 17:31:00+02";"2011-03-28 17:35:00+02";"00:04:00"<br />

List<strong>in</strong>g A.3: Abfrage <strong>der</strong> Lücken <strong>in</strong> <strong>der</strong> Datenaufzeichnung<br />

List<strong>in</strong>g A.2 zeigt die Abfragen für die Tabelle 1.3 auf Seite 5. List<strong>in</strong>g A.3<br />

zuerst die Abfrage <strong>der</strong> m<strong>in</strong>imalen, <strong>durch</strong>schnittlichen und maximalen Länge<br />

e<strong>in</strong>er Lücke <strong>in</strong> den Aufzeichnungen. Danach zeigt es die Abfrage die die Lücken<br />

selbst ausgibt. Dazu wird jedem Timestamp se<strong>in</strong> Nachfolger zugeordnet und<br />

dann werden alle Paare angezeigt die mehr als e<strong>in</strong>e M<strong>in</strong>ute ause<strong>in</strong>an<strong>der</strong> liegen.<br />

Die Ausgabe (14 Lücken) ist darunter als Kommentar e<strong>in</strong>gefügt.<br />

© Andreas Redmer — 29. September 2011 iii


A. Anhang: SQL Anfragen<br />

A.3. Prüfung <strong>der</strong> Korrektheit <strong>der</strong> Daten<br />

1 --constra<strong>in</strong>t 1<br />

2 select count (*)from l<strong>in</strong>ks where lq >0 or lqn >0;<br />

3 -- Ausgabe: 278850737<br />

4<br />

5 --constra<strong>in</strong>t 2<br />

6 select count (*)from l<strong>in</strong>ks where lq


A. Anhang: SQL Anfragen<br />

A.4. Maximale Knotenanzahl auf kürzesten<br />

Pfaden<br />

1 CREATE OR REPLACE FUNCTION path(<strong>in</strong>et,varchar[]) RETURNS <strong>in</strong>et[]<br />

2 AS $$ DECLARE p INET; pa INET[];<br />

3 BEGIN<br />

4 SELECT m<strong>in</strong>(pred) INTO p FROM (<br />

5 SELECT (str<strong>in</strong>g_to_array(unnest, ’,’))[1] as node,<br />

6 (str<strong>in</strong>g_to_array(unnest, ’,’))[2] as pred<br />

7 FROM unnest($2) ) as s1<br />

8 WHERE node = host($1);<br />

9 IF p IS NOT NULL THEN pa = pa || path(p,$2); END IF;<br />

10 RETURN p||pa;<br />

11 END;<br />

12 $$ STABLE LANGUAGE plpgsql;<br />

13<br />

14 SELECT avg(array_length),max(array_length) FROM<br />

15 (<br />

16 select s1.time, s2.nodea, array_length(<br />

17 path(s2.nodea,array_agg(host(node)||’,’||host(pred))),1)<br />

18 from(<br />

19 select t.time,(shortestpaths(t.time)).* from times t<br />

20 where extract (m<strong>in</strong>ute from time)=0 and<br />

21 time >= (select max (time) from times)-<strong>in</strong>terval’14 days’<br />

22 ) as s1,<br />

23 (<br />

24 -- selects 189 nodes<br />

25 select dist<strong>in</strong>ct nodea from l<strong>in</strong>ks<br />

26 where extract (m<strong>in</strong>ute from time)=0 and<br />

27 time >= (select max (time) from times)-<strong>in</strong>terval’14 days’<br />

28 ) as s2<br />

29 GROUP BY s1.time, s2.nodea<br />

30 ) as s3;<br />

31 --Ausgabe : avg:4.8187159440890784, max: 19<br />

List<strong>in</strong>g A.5: Abfrage <strong>der</strong> Durchschnittlichen und maximalen Pfadlänge <strong>der</strong><br />

kürzesten Pfade<br />

List<strong>in</strong>g A.5 zeigt e<strong>in</strong>e Abfrage, die die durschnittliche und maximale Anzahl<br />

<strong>der</strong> Knoten auf den gefundenen kürzesten Wegen bestimmt. Dafür wird die<br />

gleiche path-Funktion verwendet, die auch <strong>in</strong> List<strong>in</strong>g 5.5 (Seite 79) verwendet<br />

wird. Es wurden nur Timestamps betrachtet, die <strong>in</strong> den letzten zwei Wochen<br />

<strong>der</strong> Aufzeichnung zu e<strong>in</strong>er vollen Stunde aufgezeichnet wurden.<br />

Wie ist das Verhältnis von maximaler Pfadlänge zur Knotenanzahl?<br />

Für die Komplexitätbetrachtung ist es wichtig zu wissen, welches Verhältnis<br />

zwischen Knotenanzahl (n) und maximaler Pfadlänge besteht. Im kartesischen<br />

E<strong>in</strong>heitgitter 18 (wie z. B. <strong>in</strong> Abbildung A.1) enthält e<strong>in</strong> kürzester Weg nie mehr<br />

als 2 √ n Knoten.<br />

18 E<strong>in</strong> quadratisches Gitter <strong>in</strong>dem nur horizontale und vertikale Kanten existieren, <strong>der</strong>en<br />

Kantengewichte alle gleich 1 s<strong>in</strong>d.<br />

© Andreas Redmer — 29. September 2011 v


A. Anhang: SQL Anfragen<br />

N (1,1)<br />

N (1,2)<br />

N (1,3)<br />

N (1,4)<br />

N (1,5)<br />

N (1,6)<br />

N (2,1)<br />

N (2,2)<br />

N (2,3)<br />

N (2,4)<br />

N (2,5)<br />

N (2,6)<br />

N (3,1)<br />

N (3,2)<br />

N (3,3)<br />

N (3,4)<br />

N (3,5)<br />

N (3,6)<br />

N (4,1)<br />

N (4,2)<br />

N (4,3)<br />

N (4,4)<br />

N (4,5)<br />

N (4,6)<br />

N (5,1)<br />

N (5,2)<br />

N (5,3)<br />

N (5,4)<br />

N (5,5)<br />

N (5,6)<br />

N (6,1)<br />

N (6,2)<br />

N (6,3)<br />

N (6,4)<br />

N (6,5)<br />

N (6,6)<br />

N (7,1)<br />

N (7,2)<br />

N (7,3)<br />

N (7,4)<br />

N (7,5)<br />

N (7,6)<br />

N (7,7)<br />

Abbildung A.1.: E<strong>in</strong> kartesisches E<strong>in</strong>heitsgitter<br />

Wenn dort die Diagonalen e<strong>in</strong>gezeichnet wären, wäre die obere Schranke nur<br />

noch √ n Knoten. Deshalb wurde angenommen, dass dieses Verhältnis auch für<br />

die betrachteten <strong>Netzwerkgraphen</strong> e<strong>in</strong> Wurzelverhältnis <strong>der</strong> Form<br />

x√ n<br />

ist.<br />

In List<strong>in</strong>g A.5 werden 189 Knoten selektiert. Dies ist die Anzahl <strong>der</strong> Knoten<br />

die im betrachten Zeitraum aktiv waren. Im Ergebnis waren maximal 19<br />

Knoten auf e<strong>in</strong>em kürzesten Pfad zu f<strong>in</strong>den. Also gilt:<br />

und daraus folgt:<br />

x√<br />

189 = 19 =⇒ 189<br />

1<br />

x = 19<br />

x =<br />

log 189<br />

log 19<br />

≈ 1, 78.<br />

Dadruch gibt es für diesem Zeitraum maximal<br />

1,78 √ n<br />

Knoten die auf e<strong>in</strong>em kürzesten Pfad liegen. Allerd<strong>in</strong>gs ist <strong>der</strong> <strong>durch</strong>schnittliche<br />

Wert 3,33√ n (nach dem selben Verfahren berechnet) noch wesentlich ger<strong>in</strong>ger.<br />

© Andreas Redmer — 29. September 2011 vi


A. Anhang: SQL Anfragen<br />

A.5. Floyd-Warshall-Berechnung <strong>in</strong> SQL<br />

1 -- set_w<br />

2 CREATE FUNCTION set_w(varchar, varchar, real) RETURNS void AS $$<br />

3 UPDATE nodematrix SET w = $3 WHERE (nodea,nodeb)=($1,$2);<br />

4 $$ LANGUAGE SQL;<br />

5<br />

6 -- set_d<br />

7 CREATE FUNCTION set_d(varchar, varchar, real) RETURNS void AS $$<br />

8 UPDATE nodematrix SET d = $3 WHERE (nodea,nodeb)=($1,$2);<br />

9 $$ LANGUAGE SQL;<br />

10<br />

11 -- get_d<br />

12 CREATE OR REPLACE FUNCTION get_d(varchar, varchar) RETURNS real AS $$<br />

13 SELECT d FROM nodematrix WHERE (nodea,nodeb)=($1,$2);<br />

14 $$ LANGUAGE SQL;<br />

15<br />

16 -- m<strong>in</strong>-Funktion die NULL als unendlich gross betrachtet<br />

17 CREATE FUNCTION m<strong>in</strong>(real, real) RETURNS real AS $$<br />

18 BEGIN<br />

19 IF (($1 IS NULL) AND ($2 IS NULL)) THEN RETURN NULL; END IF;<br />

20 IF ($1 IS NULL) THEN RETURN $2; END IF;<br />

21 IF ($2 IS NULL) THEN RETURN $1; END IF;<br />

22 IF ($1 < $2) THEN RETURN $1; END IF;<br />

23 RETURN $2;<br />

24 END;<br />

25 $$ LANGUAGE ’plpgsql’;<br />

26<br />

27 -- Floyd-Warshall Funktion<br />

28 CREATE FUNCTION floyd(varchar) RETURNS boolean AS $$<br />

29<br />

30 -- fill weights (both directions)<br />

31 select set_w(nodea, nodeb, lq) from oneweek<br />

32 where time=’2010-09-14 09:38:02’;<br />

33 select set_w(nodeb, nodea, lqn) from oneweek<br />

34 where time=’2010-09-14 09:38:02’;<br />

35<br />

36 -- set <strong>in</strong>itial distances to weights<br />

37 UPDATE nodematrix SET d = w;<br />

38<br />

39 select<br />

40 set_d (i.name,j.name,<br />

41 m<strong>in</strong> (get_d (i.name, j.name),<br />

42 get_d (i.name, k.name) + get_d (k.name, j.name))<br />

43 )<br />

44 from nodes i,nodes j,nodes k ;<br />

45<br />

46 select true;<br />

47 $$ LANGUAGE SQL;<br />

List<strong>in</strong>g A.6: Floyd-Warshall Berechnung <strong>in</strong> SQL<br />

List<strong>in</strong>g A.6 zeigt e<strong>in</strong>e Funktion (floyd), die den Floyd-Warshall-Algorithmus<br />

berechnet und das Ergebnis <strong>in</strong> die Tabelle nodematrix schreibt. Zuvor wird e<strong>in</strong>e<br />

verän<strong>der</strong>te m<strong>in</strong>-Funktion und Getter- und Setter-Funktionen für die Tabelle<br />

nodematrix angelegt.<br />

© Andreas Redmer — 29. September 2011 vii


A. Anhang: SQL Anfragen<br />

A.6. Floyd-Warshall-Berechnung mit PL/Python<br />

1 CREATE OR REPLACE FUNCTION pyfloyd (stamp character vary<strong>in</strong>g)<br />

2 RETURNS character vary<strong>in</strong>g<br />

3 AS $$<br />

4 q_create_nodelist = "(select dist<strong>in</strong>ct nodea as list from oneweek "<br />

5 q_create_nodelist += "where time=’2010-09-14 09:38:02’) "<br />

6 q_create_nodelist += "UNION "<br />

7 q_create_nodelist += "(select dist<strong>in</strong>ct nodeb from oneweek "<br />

8 q_create_nodelist += "where time=’2010-09-14 09:38:02’); "<br />

9<br />

10 q_create_l<strong>in</strong>klist = "(select nodea,nodeb,lq,lqn from oneweek "<br />

11 q_create_l<strong>in</strong>klist += "where time=’2010-09-14 09:38:02’); "<br />

12<br />

13 #-- nl = nodelist (INT --> Str<strong>in</strong>g)<br />

14 nl = plpy.execute(q_create_nodelist)<br />

15 foo = nl[0]["list"]<br />

16 n = len(nl)<br />

17<br />

18 #-- nm = l<strong>in</strong>kmap (Str<strong>in</strong>g --> INT)<br />

19 nm = dict()<br />

20 for i <strong>in</strong> range(n):<br />

21 nm[nl[i]["list"]]=i<br />

22<br />

23 #-- ll = l<strong>in</strong>klist<br />

24 ll = plpy.execute(q_create_l<strong>in</strong>klist)<br />

25<br />

26 #-- generate distance matrix d<br />

27 d = [[0 for col <strong>in</strong> range(n)] for row <strong>in</strong> range(n)]<br />

28<br />

29 #-- fill d with <strong>in</strong>f<strong>in</strong>ity<br />

30 for i <strong>in</strong> range(n):<br />

31 for j <strong>in</strong> range(n):<br />

32 d[i][j] = 99999<br />

33<br />

34 #-- INIT: add l<strong>in</strong>ks to distance matrix<br />

35 for i <strong>in</strong> range(len(ll)):<br />

36 nodea = ll[i]["nodea"]<br />

37 nodeb = ll[i]["nodeb"]<br />

38 d[nm[nodea]][nm[nodeb]] = ll[i]["lq"]<br />

39 d[nm[nodeb]][nm[nodea]] = ll[i]["lqn"]<br />

40<br />

41 for k <strong>in</strong> range(n):<br />

42 for i <strong>in</strong> range(n):<br />

43 for j <strong>in</strong> range(n):<br />

44 d[i][j] = m<strong>in</strong> (d[i][j],d[i][k] + d[k][j])<br />

45 return len(ll)<br />

46 $$ LANGUAGE plpythonu;<br />

List<strong>in</strong>g A.7: Floyd-Warshall Berechnung mit PL/Python<br />

List<strong>in</strong>g A.7 zeigt die PL/Python Funktion pyfloyd, die den Floyd-Warshall-<br />

Algorithmus berechnet. Sie gibt das Ergebnis jedoch nicht zurück, da sie nur<br />

für e<strong>in</strong>e Zeitmessung erstellt wurde.<br />

© Andreas Redmer — 29. September 2011 viii


A. Anhang: SQL Anfragen<br />

A.7. Test <strong>der</strong> General-Gateway-Strategie<br />

1 -- Compares the 2 Dijkstra implementations and returns the number<br />

2 -- of differences. This should always return 0.<br />

3 DROP FUNCTION IF EXISTS compare_algorithms(timestamptz);<br />

4 CREATE OR REPLACE FUNCTION compare_algorithms(timestamptz)<br />

5 RETURNS big<strong>in</strong>t AS<br />

6 $$<br />

7 select count (*) from<br />

8 (<br />

9 (<br />

10 select * from shortestpaths($1,1)<br />

11 EXCEPT<br />

12 select * from shortestpaths($1,2)<br />

13 )<br />

14 UNION<br />

15 (<br />

16 select * from shortestpaths($1,2)<br />

17 EXCEPT<br />

18 select * from shortestpaths($1,1)<br />

19 )<br />

20 ) s1<br />

21 $$<br />

22 STABLE LANGUAGE SQL;<br />

23<br />

24<br />

25 -- Return all timestamps and their number of differences,<br />

26 -- if there are more than 0 differences, or<strong>der</strong>ed by differences.<br />

27 select diffs, time from<br />

28 (<br />

29 select compare_algorithms(s1.time) diffs, s1.time from<br />

30 (<br />

31 select time from times<br />

32 ) as s1<br />

33 ) as s2<br />

34 where diffs>0<br />

35 or<strong>der</strong> by diffs DESC;<br />

36 -- Ausgabe: 0 Zeilen<br />

List<strong>in</strong>g A.8: Anzahl Unterschiede zwischen herkömmlicher und General Gateway<br />

Strategie<br />

List<strong>in</strong>g A.8 zeigt e<strong>in</strong>e Abfrage, die die Ergebnisse mit <strong>der</strong> General Gateway<br />

Strategie mit denen <strong>der</strong> herkömmlichen Strategie vergleicht. Dazu wird e<strong>in</strong><br />

optionaler Parameter <strong>in</strong> <strong>der</strong> shortestPaths-Funktion verwendet, mit dem die<br />

Strategie e<strong>in</strong>stellbar ist. Es werden alle Timestamps selektiert bei denen die<br />

General Gateway Strategie e<strong>in</strong> an<strong>der</strong>es Ergebnis liefert als die herkömmliche<br />

Strategie. Es werden 0 Zeilen ausgegeben. Somit liefert die General Gateway<br />

Strategie für alle vorhandenen Timestamps das gleiche Ergebnis, das auch ohne<br />

ihre Verwendung entstehen würde.<br />

© Andreas Redmer — 29. September 2011 ix


A. Anhang: SQL Anfragen<br />

A.8. Implementierung <strong>der</strong> Algebra aus Abschnitt<br />

4.1.4<br />

1 -- e<strong>in</strong> (Knoten, Vorgaenger) Tupel<br />

2 CREATE TYPE t AS (v varchar, v_p varchar );<br />

3<br />

4 -- e<strong>in</strong> Dijkstra-Ergebnis<br />

5 CREATE TYPE D AS (results t[]);<br />

6<br />

7 -- e<strong>in</strong> Unterschied (M,P)<br />

8 CREATE TYPE u AS ( M D, P D);<br />

9<br />

10 -- Tabelle, die d. Unterschied zw. 2 Timestamps speichern kann<br />

11 CREATE TABLE unterschiede ( t_a timestamp, t_b timestamp, u u );<br />

12<br />

13 -- diff-Funktion<br />

14 CREATE FUNCTION _diff (D D,u u) RETURNS D<br />

15 AS $$ DECLARE res D;<br />

16 BEGIN<br />

17 SELECT array_agg(<br />

18 ((SELECT unnest (D.results) EXCEPT<br />

19 SELECT unnest (u.M))<br />

20 UNION SELECT unnest (u.P)))<br />

21 INTO res.results;<br />

22 RETURN res;<br />

23 END; $$<br />

24 LANGUAGE plpgsql;<br />

25<br />

26 -- Inverse <strong>der</strong> diff-Funktion<br />

27 CREATE FUNCTION _<strong>in</strong>verse_diff (D D,u u) RETURNS D<br />

28 AS $$ DECLARE res D;<br />

29 BEGIN<br />

30 SELECT array_agg(<br />

31 ((SELECT unnest (D.results) EXCEPT<br />

32 SELECT unnest (u.P))<br />

33 UNION SELECT unnest (u.M)))<br />

34 INTO res.results;<br />

35 RETURN res;<br />

36 END; $$<br />

37 LANGUAGE plpgsql;<br />

38<br />

39 -- Operatordef<strong>in</strong>itionen<br />

40 CREATE OPERATOR + ( leftarg = D, rightarg = u,<br />

41 procedure = _diff, commutator = + );<br />

42<br />

43 CREATE OPERATOR - ( leftarg = D, rightarg = u,<br />

44 procedure = _<strong>in</strong>verse_diff, commutator = - );<br />

List<strong>in</strong>g A.9: Die Algebra aus Abschnitt 4.1.4 <strong>in</strong> PL/pgSQL<br />

List<strong>in</strong>g A.9 zeigt die Algebra aus Abschnitt 4.1.4 <strong>in</strong> PL/pgSQL implementiert.<br />

Bei <strong>der</strong> Ausführung von List<strong>in</strong>g A.9 werden nur Datenstrukturen und<br />

Funktionen auf dem Datenbankserver angelegt. Diese können danach im Datenbanksystem<br />

verwendet werden. Es werden ke<strong>in</strong>e Berechnungen <strong>durch</strong>geführt.<br />

© Andreas Redmer — 29. September 2011 x


B. Anhang: Suche nach e<strong>in</strong>er Partitionierung<br />

B. Anhang: Suche nach e<strong>in</strong>er<br />

Partitionierung<br />

An dieser Stelle soll e<strong>in</strong>e neue Problemstellung erklärt werden, die mit dieser<br />

Arbeit im Zusammenhang steht. Im Opennet arbeiten alle Knoten auf dem<br />

selben WLAN-Kanal (Kanal 1). Dies führt zu Interferenzen und bee<strong>in</strong>trächtigt<br />

unter Umständen die Netzwerkqualität. In [32] wurde versucht mittels<br />

Brute-Force e<strong>in</strong>e Partitionierung des Graphen zu berechnen. Wenn geeignete<br />

Partitionen gefunden werden, können diese auf getrennten Kanälen arbeiten<br />

und so die Netzwerkqualität erhöhen. Im Folgenden soll e<strong>in</strong> Ansatz erklärt werden,<br />

<strong>der</strong> die Partitionierung mit den <strong>in</strong> dieser Arbeit entwickelten Funktionen<br />

ermöglicht.<br />

Aus Platzgründen wird hier ke<strong>in</strong> SQL-Quelltext mehr abgebildet. Die Funktionen<br />

werden formal beschrieben. Ziel ist es den <strong>Netzwerkgraphen</strong> <strong>in</strong> Partitionen<br />

e<strong>in</strong>zuteilen. Die Verb<strong>in</strong>dung zwischen zwei verschiedenen Partitionen ist<br />

dabei nur über spezielle Backbone-Knoten möglich, die im Graphen vorhanden<br />

s<strong>in</strong>d. Im Folgenden wird e<strong>in</strong>e Möglichkeit <strong>der</strong> Bestimmung solcher Partitionen<br />

mit den vorhandenen UDF beschrieben. Es wird dabei lediglich SQL verwendet;<br />

die Java-UDF brauchen auch hier nicht verän<strong>der</strong>t zu werden. Gegeben sei<br />

e<strong>in</strong> Graph mit <strong>der</strong> Knotenmenge V = (v 1 , . . . , v n ) und <strong>der</strong> Kantenmenge E.<br />

Sei weiterh<strong>in</strong> G e<strong>in</strong>e Menge von Gateways für die gilt:<br />

G ⊂ V mit: G = {g 1 , g 2 , . . . }<br />

und B e<strong>in</strong>e Menge von Backbones für die gilt:<br />

B ⊂ V mit: B = {b 1 , b 2 , . . . }.<br />

Dabei müssen G und B nicht disjunkt se<strong>in</strong>. Gesucht ist e<strong>in</strong>e Partitionierungsabbildung<br />

part : (V \ B) −→ N,<br />

die jedem Knoten, <strong>der</strong> ke<strong>in</strong> Backbone ist, genau e<strong>in</strong>e Partitionsnummer zuweist.<br />

Es muss<br />

∀v x ∈ (V \ B) : part(v x ) = p i (mit: 1 ≤ i ≤ k)<br />

gelten. Dabei ist p i ∈ N e<strong>in</strong>e Partitionsnummer und k e<strong>in</strong>e vorher zu def<strong>in</strong>ierende<br />

Konstante, welche die maximale Anzahl <strong>der</strong> Partitionen festlegt. E<strong>in</strong>e<br />

Partitionierung soll gültig se<strong>in</strong>, wenn <strong>der</strong> Graph mit <strong>der</strong> Kantenmenge<br />

E \ {(v x , v y )|(part(v x ) ≠ part(v y )) ∧ v x /∈ B ∧ v y /∈ B}<br />

immernoch vollständig verbunden ist. Die Kantenmenge wird also auf Kanten<br />

reduziert, die von o<strong>der</strong> zu e<strong>in</strong>em Backbone laufen o<strong>der</strong> sich <strong>in</strong> <strong>der</strong> gleichen<br />

Partition bef<strong>in</strong>den.<br />

© Andreas Redmer — 29. September 2011 xi


B. Anhang: Suche nach e<strong>in</strong>er Partitionierung<br />

Mit <strong>der</strong> Bruteforce-Methode (also dem Probieren aller möglichen Komb<strong>in</strong>ationen<br />

aus Knoten und Partitionsnummer), könnte mit dem Zeitaufwand O(k n )<br />

e<strong>in</strong>e gültige Partitionierung gefunden werden. E<strong>in</strong>e Random-Walk-Strategie<br />

(also dem Probieren zufälliger Komb<strong>in</strong>ationen) könnte schneller e<strong>in</strong>e gültige<br />

Partitionierung f<strong>in</strong>den. Diese hat allerd<strong>in</strong>gs die Worst-Case-Komplexität<br />

O(∞). Da diese beiden Methoden für die praktische Ausführung zu zeitaufwändig<br />

s<strong>in</strong>d, soll nun e<strong>in</strong>e weitere Methode vorgestellt werden, die im Rahmen<br />

dieser Arbeit entwickelt wurde.<br />

Schritt 1: Betrachte alle Gateways als normale Knoten und alle Backbones<br />

als Gateways. Führe dann die shortestPaths-Funktion aus.<br />

v 1<br />

v 2<br />

g 1<br />

Backbones (Menge B)<br />

b 1<br />

v 3 v 4<br />

b 2<br />

v 5 v 6<br />

b 3<br />

g 2<br />

v 7<br />

v 8 b 4<br />

Abbildung B.1.: Beispielgraph: Alle kürzesten Wege <strong>in</strong> die Menge <strong>der</strong><br />

Backbones<br />

Schritt 2: Wähle alle Knoten, die als direkten Vorgänger e<strong>in</strong> Backbone<br />

haben, aus und betrachte sie und die daran angehängten Knoten als e<strong>in</strong>e Partition.<br />

In Abbildung B.1 wurde dies an e<strong>in</strong>em Beispiel <strong>durch</strong>geführt. Dabei<br />

bilden die Knoten g 1 , v 4 , v 6 und v 8 die Ausgangsknoten für die Partitionen. Es<br />

entstehen also vier Partitionen. Diese s<strong>in</strong>d als gestrichelte L<strong>in</strong>ie gekennzeichnet.<br />

Schritt 3 (optional): Reduziere die Partitionen. Im Opennet s<strong>in</strong>d <strong>der</strong>zeit<br />

60 Backbone-Knoten def<strong>in</strong>iert. Mit dieser Methode entstehen also sehr viele<br />

Partitionen. Diese werden nun zusammengefasst. Um die Implementierung an<br />

dieser Stelle e<strong>in</strong>fach zu halten, geschieht dies nach dem Zufallspr<strong>in</strong>zip. Dabei<br />

wird mit e<strong>in</strong>er Modulo-Operation auf e<strong>in</strong>er Zufallszahl (modulo k) dafür<br />

gesorgt, dass die maximale Anzahl <strong>der</strong> gewünschten Partitionen nicht überschritten<br />

wird. Später sollte man sich evtl. Gedanken über e<strong>in</strong>e geschicktere<br />

© Andreas Redmer — 29. September 2011 xii


B. Anhang: Suche nach e<strong>in</strong>er Partitionierung<br />

Zusammenfassung <strong>der</strong> Partitionen machen. Beispielsweise könnte man immer<br />

Partitionen zusammenfassen, die bezüglich ihrer geographischen Koord<strong>in</strong>aten<br />

möglichst weit vone<strong>in</strong>an<strong>der</strong> entfernt s<strong>in</strong>d. Damit würde man sicherstellen, dass<br />

die Partitionen sich über das gesamte geographische Gebiet des Netzwerkes<br />

erstrecken. Im Beispiel <strong>in</strong> Abbildung B.1 entstanden nur vier Partitionen, so<br />

dass diese nicht weiter zusammengefasst wurden.<br />

v 1<br />

v 2<br />

g 1<br />

Backbones (Menge B)<br />

b 1<br />

v 3 v 4<br />

b 2<br />

v 5 v 6<br />

b 3<br />

g 2<br />

v 7<br />

v 8 b 4<br />

Abbildung B.2.: Beispielgraph: Alle kürzesten Wege zu den Gateways ohne<br />

Beachtung <strong>der</strong> Partitionen<br />

Schritt 4: Streiche alle Kanten, die zwei Knoten aus verschiedenen Partitionen<br />

mite<strong>in</strong>an<strong>der</strong> verb<strong>in</strong>den. In Abbildung B.2 wurden die kürzesten Wege<br />

e<strong>in</strong>gezeichnet, die <strong>der</strong> Dijkstra-Algorithmus bei <strong>der</strong> normalen Ausführung auf<br />

dem Graphen f<strong>in</strong>den würde. Die Kanten, die nun nicht mehr verwendet werden<br />

dürfen und somit gestrichen werden müssen, wurden rot gekennzeichnet.<br />

Schritt 5: Berechne abschließend (mit <strong>der</strong> reduzierten Kantenmenge) die<br />

kürzesten Pfade mit <strong>der</strong> shortestPaths-Funktion. Wenn je<strong>der</strong> Knoten mit<br />

e<strong>in</strong>em Gateway verbunden werden kann (also e<strong>in</strong>en Vorgänger zugewiesen bekommt),<br />

ist die Partitionierung gültig. Der Algorithmus ist beendet. In Abbildung<br />

B.3 wurden die neuen Pfade, die <strong>der</strong> Dijkstra-Algorithmus nun gefunden<br />

hat, grün e<strong>in</strong>gezeichnet. In <strong>der</strong> Implementation mit dem Opennet-<br />

Netzwerkgraph war die Partitionierung für mit den 60 Backbones für k = 3<br />

immer gültig. Da die Zufallszahlen <strong>in</strong> Schritt 3 gleichverteilt s<strong>in</strong>d, ist es sehr<br />

unwahrsche<strong>in</strong>lich, dass e<strong>in</strong>e solche Partitionierung e<strong>in</strong>en Teilgraphen vollständig<br />

abtrennt.<br />

© Andreas Redmer — 29. September 2011 xiii


B. Anhang: Suche nach e<strong>in</strong>er Partitionierung<br />

v 1<br />

v 2<br />

g 1<br />

Backbones (Menge B)<br />

b 1<br />

v 3 v 4<br />

b 2<br />

v 5 v 6<br />

b 3<br />

g 2<br />

v 7<br />

v 8 b 4<br />

Abbildung B.3.: Beispielgraph: Alle kürzesten Wege zu den Gateways unter<br />

Beachtung <strong>der</strong> Partitionen<br />

E<strong>in</strong>e so gefundene Partitionierung ist wahrsche<strong>in</strong>lich besser, als e<strong>in</strong>e <strong>durch</strong><br />

Brute-Force o<strong>der</strong> Random-Walk entdeckte, da schon im ersten Schritt nur die<br />

besten Wege <strong>in</strong> die Menge <strong>der</strong> Backbones gewählt werden. Dort wären allerd<strong>in</strong>gs<br />

noch viele weitere Wege möglich. Beispielsweise hätte v 4 auch über g 1<br />

den Knoten b 1 erreichen können.<br />

Es stellt sich also die Frage, wie die Partitionierung <strong>durch</strong> geschicktes Zusammenfassen<br />

<strong>in</strong> Schritt 3 optimiert werden kann. Im Beispiel <strong>in</strong> Abbildung<br />

B.3 wäre es offensichtlich gut gewesen, die oberen beiden Partitionen zusammen<br />

zu fassen und damit die direkte Verb<strong>in</strong>dung von v 4 zu g 1 zu erhalten.<br />

Um die Güte e<strong>in</strong>er gültigen Partitionierung zu messen, können verschiedene<br />

Fitness-Funktionen für die gefundenen Partitionen def<strong>in</strong>iert werden. Beispielsweise<br />

kann ermittelt werden:<br />

ˆ Welche Qualität haben die gestrichenen Kanten?<br />

ˆ Welche Qualität haben die nicht gestrichenen Kanten?<br />

ˆ Wie viele Kanten wurden <strong>durch</strong> die Partitionierung gestrichen?<br />

ˆ Wie viele Kanten, die <strong>in</strong> den tatsächlichen kürzesten Wegen vorhanden<br />

s<strong>in</strong>d, wurden <strong>durch</strong> die Partitionierung gestrichen?<br />

Auch Komb<strong>in</strong>ationen davon und weitere Fitness-Funktionen s<strong>in</strong>d denkbar.<br />

Im Rahmen dieser Arbeit wurde die zuletzt aufgezählte Möglichkeit implementiert.<br />

Dazu sei D E die Ausgabemenge des Dijkstra-Algorithmus auf e<strong>in</strong>em<br />

Graphen mit <strong>der</strong> Kantenmenge E, dann beschreibt die Funktion<br />

fitness(part) := |D E ∩ D E\{(vx,v y)|(part(v x)≠part(v y))∧v x /∈B∧v y /∈B}|<br />

© Andreas Redmer — 29. September 2011 xiv


B. Anhang: Suche nach e<strong>in</strong>er Partitionierung<br />

die implementierte Funktion. Dabei wird praktisch die Ausgabe <strong>der</strong> unverän<strong>der</strong>ten<br />

shortestPaths-Funktion mit <strong>der</strong> Augabe e<strong>in</strong>er an<strong>der</strong>en shortest-<br />

Paths-Funktion, die Kanten streichen kann (analog zu List<strong>in</strong>g 5.7) verglichen.<br />

Die M<strong>in</strong>imierung <strong>der</strong> Fitness-Funktion ist die Optimierung <strong>der</strong> Partitionierung.<br />

Es gilt<br />

m<strong>in</strong>(fitness(part)) = 0.<br />

Wenn die Fitness-Funktion also auf 0 m<strong>in</strong>imiert werden kann, dann ist die<br />

<strong>durch</strong> part beschriebene Partitionierung optimal und kann nicht weiter verbessert<br />

werden. Im Rahmen dieser Arbeit wurden die Zufallszahlen <strong>in</strong> Schritt<br />

3 beibehalten und die Fitness-Funktion <strong>durch</strong> wie<strong>der</strong>holte Ausführungen m<strong>in</strong>imiert.<br />

Dies entspricht e<strong>in</strong>er Random-Walk-Strategie.<br />

Schritt 6 (optional): Wie<strong>der</strong>hole Schritt 1 bis 5 viele Male. Speichere<br />

dabei jeweils den niedrigsten gefundenen Wert <strong>der</strong> Fitness-Funktion und gib<br />

die zugehörige Partitionierung am Ende aus. Brich die Ausführung vorher ab,<br />

wenn die Fitness-Funktion e<strong>in</strong>en gewissen Wert unterschreitet o<strong>der</strong> 0 ist.<br />

Fazit: Es wurde <strong>der</strong> Timestamp ”<br />

2010-09-14 14:00:02“ betrachtet. Es sollten<br />

drei Partitionen gefunden werden (also k = 3). Nach 30 Sekunden Laufzeit war<br />

die Partitionierung optimal (also fitness = 0), so dass die kürzesten Wege<br />

sich <strong>durch</strong> die Partitionierung nicht geän<strong>der</strong>t haben. In Abbildung B.4 s<strong>in</strong>d<br />

die entstandenen Partitionen visualisiert. Dar<strong>in</strong> ist zu erkennen, dass ke<strong>in</strong>e<br />

direkten Verb<strong>in</strong>dungen zwischen zwei Partitionen bestehen, und dass dennoch<br />

je<strong>der</strong> Knoten e<strong>in</strong>en Pfad zu e<strong>in</strong>em Gateway hat. Verb<strong>in</strong>dungen zwischen zwei<br />

Partitionen führen nun immer über m<strong>in</strong>destens e<strong>in</strong>en Backbone-Knoten.<br />

In Zukunft müsste nun überprüft werden, ob e<strong>in</strong>e solche Partitionierung<br />

s<strong>in</strong>nvoll ist. Sie ist nun zwar mathematisch optimal, aber das könnte auch bedeuten,<br />

dass sich die Partitionen geographisch gar nicht überschneiden. Die<br />

Partitionen müssten nun also auf die geographisch korrekten Positionen <strong>der</strong><br />

Knoten e<strong>in</strong>gezeichnet werden um zu prüfen wie stark sie sich überschneiden.<br />

Nur so kann sichergestellt werden, dass <strong>durch</strong> die Partitionierung auch Interferenzen<br />

vermieden werden. Unter Umständen muss <strong>der</strong> Algorithmus (wie<br />

<strong>in</strong> Schritt 3 schon angedeutet) um e<strong>in</strong>ige geographische Parameter erweitert<br />

werden.<br />

© Andreas Redmer — 29. September 2011 xv


B. Anhang: Suche nach e<strong>in</strong>er Partitionierung<br />

Partition 1<br />

192.168.1.123<br />

192.168.1.125<br />

192.168.1.109 192.168.1.54<br />

192.168.1.190<br />

192.168.1.178<br />

192.168.1.134<br />

192.168.1.156<br />

192.168.1.163<br />

192.168.1.193<br />

192.168.1.204<br />

192.168.1.69<br />

192.168.1.6<br />

192.168.1.82<br />

192.168.1.195<br />

192.168.1.84<br />

192.168.1.168<br />

192.168.2.6<br />

192.168.1.94<br />

192.168.1.28<br />

192.168.33.220<br />

192.168.1.96<br />

192.168.2.5<br />

192.168.1.29<br />

192.168.1.33<br />

192.168.1.4<br />

192.168.33.12<br />

192.168.1.5<br />

192.168.2.8<br />

192.168.1.113<br />

192.168.1.52<br />

192.168.1.14<br />

192.168.1.129<br />

192.168.1.99<br />

192.168.2.4<br />

192.168.1.39<br />

192.168.1.25<br />

192.168.1.10<br />

192.168.1.171<br />

192.168.2.2<br />

192.168.10.2<br />

GW<br />

192.168.1.103<br />

192.168.1.124<br />

192.168.1.32<br />

192.168.1.16<br />

192.168.1.172<br />

192.168.1.104<br />

192.168.2.3<br />

192.168.1.15<br />

192.168.1.205<br />

192.168.1.138<br />

192.168.2.7<br />

192.168.1.120 192.168.1.70<br />

192.168.1.126<br />

192.168.1.46<br />

192.168.1.159<br />

192.168.1.182<br />

192.168.1.184<br />

192.168.1.189 192.168.1.185<br />

192.168.1.186<br />

192.168.0.244<br />

192.168.1.150<br />

Partition 2<br />

192.168.1.140<br />

192.168.1.166 192.168.1.155<br />

192.168.1.147<br />

192.168.1.187<br />

192.168.1.180<br />

192.168.1.197<br />

192.168.1.122<br />

192.168.1.143<br />

192.168.1.188<br />

192.168.1.133<br />

192.168.1.22<br />

192.168.1.71<br />

192.168.1.208<br />

192.168.1.112<br />

192.168.1.111<br />

192.168.1.222<br />

192.168.1.132<br />

Partition 0<br />

192.168.1.35<br />

192.168.1.58<br />

192.168.1.89<br />

192.168.1.77<br />

192.168.1.106<br />

192.168.1.79<br />

192.168.1.157<br />

192.168.1.248 192.168.1.245<br />

192.168.1.165<br />

192.168.1.121<br />

192.168.1.177<br />

192.168.1.247<br />

192.168.1.167<br />

192.168.1.49<br />

192.168.1.149 192.168.1.158<br />

192.168.1.20<br />

192.168.1.242<br />

192.168.1.239<br />

192.168.1.238<br />

192.168.1.241<br />

192.168.1.236<br />

192.168.1.235<br />

192.168.1.207<br />

192.168.1.80<br />

192.168.1.67<br />

192.168.1.26<br />

192.168.1.214<br />

192.168.1.93<br />

192.168.1.240<br />

192.168.1.98<br />

192.168.1.215<br />

192.168.1.91<br />

GW<br />

10.2.1.1<br />

192.168.1.38<br />

192.168.1.218<br />

192.168.1.216<br />

192.168.1.226<br />

192.168.1.36<br />

192.168.1.223<br />

192.168.1.213<br />

192.168.1.43<br />

192.168.1.27<br />

192.168.1.75<br />

192.168.1.55<br />

192.168.11.181<br />

192.168.1.76<br />

192.168.1.139<br />

192.168.1.86<br />

192.168.1.73<br />

192.168.33.253<br />

192.168.1.81<br />

192.168.1.83<br />

192.168.1.57<br />

192.168.1.12<br />

192.168.1.116 192.168.1.194<br />

192.168.1.148<br />

192.168.1.62<br />

192.168.1.45<br />

192.168.1.135 192.168.1.90<br />

GW<br />

192.168.1.87<br />

192.168.1.51<br />

192.168.1.145<br />

192.168.1.130<br />

192.168.1.78<br />

192.168.1.198<br />

192.168.1.199<br />

192.168.1.56<br />

192.168.1.88<br />

192.168.1.170<br />

192.168.1.19<br />

192.168.1.196<br />

192.168.1.202<br />

192.168.1.37<br />

192.168.1.191<br />

192.168.1.31<br />

192.168.1.141<br />

192.168.1.154<br />

192.168.11.203<br />

192.168.1.174<br />

192.168.1.128<br />

192.168.1.183<br />

192.168.1.127<br />

192.168.1.176<br />

192.168.1.232<br />

192.168.1.233<br />

192.168.1.234<br />

192.168.1.179<br />

Abbildung B.4.: Optimale Partitionen am 14.09.2010 um 14 Uhr<br />

© Andreas Redmer — 29. September 2011 xvi

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!