• Keine Ergebnisse gefunden

3.5 Externe Klassendiagramme

4.1.6 Durchf¨uhrung einer Speicherplatz und Performanzmessung

Um eine effiziente Messung von Speicherplatz und Geschwindigkeit zu erm¨oglichen, wird zum Test ein Graph mit 8.000.000 zuf¨allig erstellten Kanten und 4.000.000 zuf¨allig erstellten Knoten genommen, welche wiederum mit zuf¨allig ausgew¨ahlten Knoten verbunden werden. Demzufolge werden 16.000.000 Inzidenz-Objekte erzeugt.

Jede Kante und jeder Knoten besitzt ein Attribut vom Typ String mit dem Inhalt

”test“.

Der Gesamttest wird f¨unfmal durchgef¨uhrt und die Ergebnisse gemittelt. Das Testsys-tem ist ein AMD Athlon64 mit 2.4GHz Taktfrequenz sowie 2GB RAM.

Da die JVM-Heap-Gr¨oße standardm¨aßig auf 64MB eingestellt ist und dieser Wert f¨ur die Erstellung von 28 Millionen Java-Objekten nicht ausreicht, wird sie durch den Parameter-Xmx1500Mauf 1500MB erweitert.

Der verwendete Speicherplatz eines Java-Programms l¨asst sich mittels des in JDK 5.0

4.1. Experimente bez ¨uglich des Speicher- und Laufzeitverhaltens enthaltenen Hilfsprogramms JConsole3 ermitteln. Hierzu muss die JVM mit dem Para-meter-Dcom.sun.management.jmxremotegestartet werden. JConsole erkennt den Thread automatisch und kann detaillierte Informationen ¨uber das Java-Programm anzeigen [Sun05].

Zur Messung des Laufzeitverhaltens werden im Beispielprogramm Timer installiert, welche jeweils vor und nach dem Betreten der entsprechenden Methoden die Zeitmes-sungen durchf¨uhren und anschließend die Differenzen ausgeben.

In Tabelle 4.2 wird der Speicherplatzverbrauch und das Laufzeitverhalten der beiden Versuche gegen¨uber gestellt. G O entspricht dem Graphen mit der feingranularen Ob-jektstruktur, G I und G IP entsprechen den Graphen mit der Inzidenzliste. Der Unter-schied zwischen G I und G IP besteht darin, dass G I die Inzidenz-Objekte on demand erstellt, sie werden also erst beim Zugriff auf eine Kante generiert und gespeichert. Ein erneuter Zugriff auf dieses Inzidenz-Objekt liefert anschließend das gespeicherte Ob-jekt. Bei G IP werden die Inzidenzen sofort beim Erstellen der Kante mit erstellt.

Die Experimente sind die folgenden:

• Speicher: Der Speicherplatzverbrauch des Java-Programms wird in Megabyte an-gegeben. Es wird die Gr¨oße des Heap nach einer mittels JConsole durchgef¨uhrten manuellen Garbage Collection angegeben.

• Anlegen: Die ben¨otigte Zeit in Millisekunden, welche f¨ur das Anlegen des Gra-phen ben¨otigt wird, wird hier dargestellt.

• Zugriff: Es wird ein einfacher Zugriff auf das Test-Attribut jedes Knoten und jeder Kante ausgef¨uhrt. Dazu werden die beiden globalen Listen Vseq und Eseq einmal durchlaufen.

• Knotengrad: Jeder Knoten wird nach seinem Grad gefragt. Dazu werden alle Inzidenz-Objekte einmal gez¨ahlt.

• DFS: Mittels des DFS4-Algorithmus wird der Graph einmal komplett traversiert.

Dazu besitzt jeder Knoten ein boolean-Attribut, welches angibt, ob der Knoten bereits einmal besucht wurde. Der Algorithmus wird in einer Schleife ¨uber alle nicht markierten Knoten des Graphen aufgerufen. In der einfacheren Version ruft sich der Algorithmus rekursiv f¨ur alle seine nicht markierten Nachbarknoten auf, bis schließlich alle markiert sind. Die Verwendung der rekursiven Version ist al-lerdings im Test nicht m¨oglich, da es bei mehr als ca. 5.000 Knoten und Kanten zu einemStackOverflowErrorkommt, da die Rekursionstiefe zu groß wird.

3zu finden im JDK-Installationsverzeichnis unterbin/jconsole.exe

4DFS: Depth First Search, Tiefensuche

Die iterative Version markiert den besuchten Knoten und speichert ihn stattdessen in einem Stack. Danach wird der jeweils n¨achste nicht markierte inzidente Kno-ten des soeben gespeicherKno-ten KnoKno-tens markiert und auch im Stack abgelegt. Dies wird immer weiter fortgesetzt, bis der Algorithmus einen Knoten findet, welcher keinerlei nicht markierte Knoten mehr besitzt. Daraufhin wird dieser Knoten wie-der vom Stack entfernt. Sobald keine Elemente mehr im Stack vorhanden sind, wird der n¨achste nicht markierte Knoten der globalen Knotensequenz Vseq auf diese Weise gepr¨uft.

• BFS: Tauscht man im DFS-Algorithmus den Stack gegen eine FIFO5-Queue, erh¨alt man den BFS6-Algorithmus. Auch dieser wird iterativ ausgef¨uhrt.

Beide Traversierungsalgorithmen (BFS und DFS) ergeben eine maximale Queue- bzw.

Stackl¨ange von ca. 1,61 Millionen Knoten. Um bei den Algorithmen nicht von der Java-Implementation stark beeinflusst zu werden, werden die Java-internen Datentypen java.util.Stackundjava.util.Queuenicht verwendet.

Der Stack wird mittels eines Feldes, welches Knoten speichern kann, sowie einer Z¨ahl-variable realisiert. Dadurch l¨asst sich immer das letzte Element entfernen oder ein Ele-ment oben auf den Stack legen.

Die Queue ben¨otigt auch ein Feld, welches Knoten speichern kann. Zus¨atzlich werden jedoch zwei Z¨ahlvariablen (eine f¨ur den Anfang und eine f¨ur das Ende) ben¨otigt, um die Funktionalit¨at einer Schlange nachahmen zu k¨onnen.

Speicher Anlegen Zugriff Knotengrad DFS BFS

G O 1390MB 69.610ms 687ms 1.532ms 7.188ms 8.078ms

G I 657MB** 11.000ms* 344ms 1.703ms 18.390ms 19.578ms

G IP 924MB 16.265ms 328ms 1.672ms 18.203ms 19.468ms

Tabelle 4.2: Auswertung des Speicherplatzverbrauchs sowie des Laufzeitverhaltens ei-nes Beispielgraphen

In Tabelle 4.2 sieht man deutliche Unterschiede zwischen den beiden Implementierun-gen. Die Inzidenzliste zeigt Vorteile beim Speicherplatzverbrauch, starke Vorteile beim Erstellen des Graphen sowie bei den Zugriffsoperationen, ist allerdings bei der Abfrage des Knotengrades geringf¨ugig und beim Traversieren deutlich langsamer als die fein-granulare objektorientierte Implementation.

5FIFO: First In, First Out, d.h. das als erstes eingelagerte Element wird auch als erstes wieder ausgege-ben

6BFS = Breadth First Search, Breitensuche

4.1. Experimente bez ¨uglich des Speicher- und Laufzeitverhaltens Die Entscheidung, Inzidenz-Objekte erst bei Gebrauch zu erzeugen, resultiert in einer um 5 Sekunden schnelleren Generierung des Graphen (siehe *). Diese Zeitersparnis ist allerdings mit Vorbehalt zu betrachten, da sie beim Zugriff auf die Inzidenzen wie-der relativiert wird. Bei wie-der erstmaligen Durchf¨uhrung eines DFS dauert wie-der Vorgang dadurch nicht rund 18 Sekunden, sondern ca. 23 Sekunden. Die restliche minimale Ab-weichung zwischen den Graphen G I und G IP liegt lediglich an der ¨Uberpr¨ufung, ob das Inzidenz-Objekt bereits generiert wurde.

Derselbe Gedankengang gilt f¨ur den Speicherplatzverbrauch (siehe **): L¨auft z.B. der DFS-Algorithmus in G I einmal ab, existiert kein Unterschied mehr zum Graphen G IP, beide belegen 924MB RAM.

Die Differenzen im Speicherplatz beruhen auf den Objekt-Referenzen in den Kanten-und Knoten-Objekten (z.B. existiert in der Inzidenzliste lediglich ein Feld mit einem Integerwert f¨ur die Referenz auf die n¨achsten Kante, in der objektorientierten Realisie-rung des Graphen bildet dies eine Referenz auf ein anderes Objekt). Die Referenz auf das vorherige Kanten- oder Knotenobjekt fehlt in der Inzidenzliste dagegen v¨ollig.

Die Unterschiede der Inzidenzlisten gegen¨uber dem feingranularen Graphen wurden zus¨atzlich mittels des Extensible Java Profiler [Vau04] untersucht.

Der Unterschied in der Traversierungszeit der beiden Implementationen l¨asst sich da-durch erkl¨aren, dass die Inzidenzliste immer per Delegation ihren Graphen um Objekte fragen muss (welcher dann in seiner Inzidenzliste nach schaut), w¨ahrend bei der ob-jektorientierten Realisierung direkt zu den entsprechenden Objekten navigiert werden kann.

Zusammenfassung und Implementationsentscheidung

Abschließend bleibt zu sagen, dass es keine beste L¨osung f¨ur dieses Problem gibt, beide L¨osungswege (objektorientiert und interne Inzidenzfelder) haben Vor- und Nachteile.

Aus diesem Grunde wird es dem Benutzer selbst ¨uberlassen, f¨ur welche Implementa-tion er sich entscheidet. Als Standard wird das Inzidenzarray genommen. M¨ochte der Anwender diese Einstellung ¨andern, muss er lediglich vor dem commit die changeIm-pl-Methode der Schema-Klasse aufrufen. Als Parameter wird der Package-Name der Implementation ¨ubergeben (also z.B. oo oder incarray).

4.2 Objektorientierte Zugriffsschicht

Wie in Kapitel 1.3 beschrieben, wird die M¨oglichkeit gesucht, an jedem Graphen, jeder Kante und jedem Knoten sowohl tempor¨are als auch persistente Attribute speichern zu k¨onnen. Dieser Abschnitt befasst sich mit der Erstellung der objektorientierten Zugriffs-schicht (kurz: OOAccess), welche in erster Linie den Zugriff auf die Attribute kapseln kann.

Die OOAccess-Schicht wurde in Abschnitt 1.4.4 bereits grob eingef¨uhrt, an dieser Stelle soll der Grund f¨ur solch eine Schicht noch einmal genau dargelegt werden. Anschlie-ßend findet eine detaillierte Beschreibung dieser Schicht statt.

Diese objektorientierte Zugriffsschicht soll zusammengefasst folgendes bieten:

• Der Zugriff auf JGraLab-Attribute soll mittels Getter und Setter erm¨oglicht wer-den, damit der umst¨andliche Weg ¨uber Attribut-Objekte weg f¨allt.

• Kanten, Knoten und auch Graphen sollen Instanzen ihrer Klasse sein, anstelle von Objekten, welche lediglich auf ihren Typ verweisen.

• Die Methodennamen zur Navigation innerhalb der Graphenlisten (Vseq, Eseq, Iseq) sollen konkret auf die Namen der Instanzen angepasst werden, anstatt diese Listen ¨uber Methodenparameter einzuschr¨anken.

• Die Erstellung der Graphen, Knoten und Kanten soll mittels Fabrik-Methoden in die sie aggregierenden Klassen verschoben werden, anstelle ihre Konstruktoren zu benutzen.