1.2 Garbage-Collection
1.2 Garbage-Collection
1.2 Garbage-Collection
Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.
YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.
Fachbereich Informatik<br />
Integrierte Schaltungen und Systeme<br />
Prof. Dr.-Ing. Sorin Huss<br />
Diplomarbeit<br />
Entwicklung eines Gargbage-Collectors für<br />
rekonfigurierbare Hardwarestrukturen<br />
Felix Madlener<br />
madlener@iss.tu-darmstadt.de<br />
Matr.-Nr.: 948463<br />
Betreuer : Dipl.-Inform. Andreas Kühn<br />
Abgabe : 8.6.2005
Zusicherungen zur Diplomarbeit gemäß §19 Abs. 6 der<br />
Diplomprüfungsordung<br />
Hiermit versichere ich, die vorliegende Diplomarbeit ohne Hilfe Dritter nur mit den<br />
angegebenen Quellen und Hilfsmitteln angefertigt zu haben. Alle Stellen, die aus Quellen<br />
entnommen wurden, sind als solche kenntlich gemacht worden. Diese Arbeit hat in<br />
gleicher oder ähnlicher Form noch keiner Prüfungsbehörde vorgelegen.<br />
Darmstadt, den 8.6.2005<br />
Felix Madlener
Inhaltsverzeichnis<br />
Abbildungsverzeichnis<br />
v<br />
1 Einleitung 1<br />
1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1<br />
<strong>1.2</strong> <strong>Garbage</strong>-<strong>Collection</strong> . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3<br />
1.3 Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3<br />
2 Begriffe und Strukturen 5<br />
2.1 Konfigurationsmöglichkeiten eines FPGA . . . . . . . . . . . . . . . . 5<br />
2.2 Objektorientierte Konzepte . . . . . . . . . . . . . . . . . . . . . . . . 6<br />
2.3 Strukturierung der Hardware . . . . . . . . . . . . . . . . . . . . . . . 8<br />
2.3.1 Layer 1: Logik-Ebene . . . . . . . . . . . . . . . . . . . . . . 8<br />
2.3.2 Layer 2: Struktur-Ebene . . . . . . . . . . . . . . . . . . . . . 9<br />
2.3.3 Layer 3: Protokolle . . . . . . . . . . . . . . . . . . . . . . . . 11<br />
2.3.4 Layer 4: Middleware . . . . . . . . . . . . . . . . . . . . . . . 11<br />
2.3.5 Layer 5: Applikation . . . . . . . . . . . . . . . . . . . . . . . 12<br />
2.3.6 Layer 6: SW-Engineering . . . . . . . . . . . . . . . . . . . . 12<br />
2.4 Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13<br />
2.5 Vereinfachte Repräsentation eines Programms . . . . . . . . . . . . . . 13<br />
3 <strong>Garbage</strong>-<strong>Collection</strong> in Software 17<br />
3.1 Statische Reservierung . . . . . . . . . . . . . . . . . . . . . . . . . . 18<br />
3.2 Explizite Reservierung . . . . . . . . . . . . . . . . . . . . . . . . . . 19<br />
3.3 Referenzzählung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19<br />
3.4 Mark-Sweep <strong>Collection</strong> . . . . . . . . . . . . . . . . . . . . . . . . . . 20<br />
3.5 Erweiterungen der Mark-Sweep-<strong>Collection</strong> . . . . . . . . . . . . . . . 21<br />
3.5.1 Fragmentierung . . . . . . . . . . . . . . . . . . . . . . . . . . 21<br />
3.5.2 Nebenläufigkeit . . . . . . . . . . . . . . . . . . . . . . . . . . 22<br />
3.5.3 Generational <strong>Collection</strong> . . . . . . . . . . . . . . . . . . . . . 23
iv<br />
INHALTSVERZEICHNIS<br />
4 <strong>Garbage</strong>-<strong>Collection</strong> in Hardware 25<br />
4.1 Einordnung der <strong>Garbage</strong>-<strong>Collection</strong> . . . . . . . . . . . . . . . . . . . 26<br />
4.2 Traversierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27<br />
4.3 Fragmentierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31<br />
4.4 Nebenläufigkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32<br />
4.5 Generational <strong>Collection</strong> . . . . . . . . . . . . . . . . . . . . . . . . . . 34<br />
4.6 Der Algorithmus der Hardware-<strong>Garbage</strong>-<strong>Collection</strong> (HW-GC) . . . . . 34<br />
5 Analyse des HW-GC-Algorithmus 39<br />
5.1 Objektstruktur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40<br />
5.2 Softwarebasiertes Simulationsmodell . . . . . . . . . . . . . . . . . . . 41<br />
5.3 Qualitative Betrachtung . . . . . . . . . . . . . . . . . . . . . . . . . . 45<br />
5.3.1 Warten auf den nächsten <strong>Garbage</strong>-<strong>Collection</strong>-Zyklus . . . . . . 46<br />
5.3.2 Die Traversierung im Mark-Prozess . . . . . . . . . . . . . . . 47<br />
5.3.3 Der Sweep-Prozess . . . . . . . . . . . . . . . . . . . . . . . . 48<br />
5.3.4 Funktionalität des HW-GC-Algorithmus . . . . . . . . . . . . . 49<br />
5.4 Quantitative Betrachtung . . . . . . . . . . . . . . . . . . . . . . . . . 49<br />
5.4.1 Laufzeit der Traversierung . . . . . . . . . . . . . . . . . . . . 50<br />
5.4.2 PageMiss . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52<br />
5.5 Häufigkeit der GC-Zyklen . . . . . . . . . . . . . . . . . . . . . . . . 55<br />
6 Zusammenfassung 57<br />
6.1 Ergebnisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57<br />
6.2 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58<br />
Literaturverzeichnis 59
Abbildungsverzeichnis<br />
1.1 Flexibilität und Effizienz bei verschiedenen Architekturen . . . . . . . . 2<br />
2.1 Abstraktionsebenen der Entwurfsmethodik . . . . . . . . . . . . . . . . 9<br />
2.2 Struktur eines Virtex2 Pro FPGA . . . . . . . . . . . . . . . . . . . . . 10<br />
2.3 Schematischer Aufbau eines Objekts und der Computing Page . . . . . 12<br />
2.4 Programmablauf und Objektgraph . . . . . . . . . . . . . . . . . . . . 15<br />
3.1 Inkorrekter Objektgraph bei nebenläufiger <strong>Garbage</strong>-<strong>Collection</strong> . . . . . 23<br />
4.1 Traversierungsreihenfolge eines Objektgraphen mit sequenzieller Traversierung<br />
mit Tiefensuche (a) und nebenläufiger Traversierung (b) . . . 28<br />
4.2 Lokalisierter Bus zur <strong>Garbage</strong>-<strong>Collection</strong> . . . . . . . . . . . . . . . . 29<br />
4.3 Ablauf einer Traversierung im HW-GC-Algortihmus . . . . . . . . . . 30<br />
4.4 Algorithmus zur Hardware-<strong>Garbage</strong>-<strong>Collection</strong> in VHDL-Pseudocode . 37<br />
5.1 Objektgraphen mit verschiedener Anzahl an ausgehenden Kanten . . . 43<br />
5.2 Minimaler aufspannender Graph . . . . . . . . . . . . . . . . . . . . . 44<br />
5.3 Erreichbare Knoten in Zufallsgraphen . . . . . . . . . . . . . . . . . . 45<br />
5.4 Verschiedene Traversierungen . . . . . . . . . . . . . . . . . . . . . . 52<br />
5.5 Neuer Objektgraph nach löschen der Kante a) zum Zeitpunkt 5 . . . . . 53<br />
5.6 Neuer Objektgraph nach löschen der Kante b) zum Zeitpunkt 4 . . . . . 53
Kapitel 1<br />
Einleitung<br />
1.1 Motivation<br />
Ziel dieser Arbeit ist der Entwurf eines Verfahrens zur automatischen Speicherverwaltung<br />
(<strong>Garbage</strong>-<strong>Collection</strong>). Diese Speicherverwaltung ist Teil eines neuartigen, objektorientierten<br />
Entwicklungsansatzes für rekonfigurierbare Hardwarestrukturen.<br />
Herkömmliche Hardware ist statisch aufgebaut. Das Logikdesign und damit auch die<br />
Funktionalität müssen bereits bei der Produktion des entsprechenden Chips vollständig<br />
definiert sein. Die Funktionalität einer solchen Architektur kann dann nicht mehr<br />
geändert werden. Dynamische Strukturen und Programme, wie sie im Bereich der Softwareentwicklung<br />
üblich sind, lassen sich somit nicht direkt in einer effizienten Hardwarestruktur<br />
abbilden.<br />
Üblicherweise können vor allem datenflussintensive, aber in ihrer Funktionalität statische<br />
Programme, von den Vorteilen einer Hardwareimplementierung profitieren. Solche<br />
Vorteile sind beispielsweise eine höhere Ausführungsgeschwindigkeit und ein geringerer<br />
Stromverbrauch. Kontrollflussintensive Algorithmen erfordern hingegen die Flexibilität<br />
eines generischen Prozessors mit darauf ablaufenden Programmen.<br />
Mit der Entwicklung von dynamisch rekonfigurierbaren Hardwarestrukturen, wie zum<br />
Beispiel dem Field Programmable Gate Array (FPGA), ist es erstmals möglich auch<br />
in Hardwareimplementierungen dynamische Strukturen umzusetzen. Die Funktionalität<br />
eines FPGAs wird durch seine Konfiguration festgelegt. Sie kann beliebig modifiziert<br />
werden, indem man das FPGA mit einem vorher generierten Datenstrom rekonfiguriert.<br />
Darüberhinaus bieten FPGAs die Möglichkeit zur partiellen Rekonfiguration. Während<br />
bei einer normalen Rekonfiguration immer der gesamte Chip beeinflusst wird, erlaubt<br />
es die partielle Rekonfiguration, nur einzelne Teile des Logikdesigns im laufenden<br />
Betrieb auszutauschen. Der übrige Chip kann dabei ohne Unterbrechung weiterlaufen.<br />
Wie in Abbildung 1.1 veranschaulicht, sind diese dynamisch rekonfigurierbaren Strukturen<br />
in Bezug auf Flexibilität und Effizienz zwischen den flexiblen, aber ineffizienten
2 KAPITEL 1. EINLEITUNG<br />
Software<br />
Flexibilität<br />
Rekonfigurierbare Hardware<br />
Effizienz<br />
Hardware<br />
Abbildung 1.1: Flexibilität und Effizienz bei verschiedenen Architekturen<br />
Softwareprogrammen und den statischen, aber effizienten Hardwareimplementierungen<br />
angeordnet.<br />
Um diese Architektur sinnvoll einzusetzen, ist neben der Verfügbarkeit der Hardware<br />
auch eine Entwurfsmethodik notwendig, die es ermöglicht, die Vorteile der Plattform<br />
voll auszunutzen und umzusetzen.<br />
Die bisherigen Entwurfsmethodiken für dynamisch rekonfigurierbare Strukturen basieren<br />
auf einer Erweiterung der Werkzeuge und Konzepte, wie sie für klassische Hardware<br />
zum Einsatz kommen. Ihre Ausgangsbasis ist also ein effizienter Entwurfsprozess,<br />
der in Bezug auf die Flexibilität jedoch stark eingeschränkt ist. Mit diesen Werkzeugen<br />
können, aufgrund ihrer völlig anderen konzeptionellen Ausrichtung, die Möglichkeiten<br />
zur Flexibilität nicht zufriedenstellend umgesetzt werden.<br />
Diese Arbeit ist Teil eines Projekts zur Entwicklung einer Entwurfsmethodik, die versucht<br />
Konzepte zur Softwareentwicklung auf dynamisch rekonfigurierbare Strukturen<br />
abzubilden. Der schematischen Einordnung aus Abbildung 1.1 folgend, wird also nicht<br />
mehr ein hardwarenaher Entwurfsansatz um Rekonfigurationsmöglichkeiten erweitert,<br />
sondern Ansätze zur flexiblen Entwicklung von Software werden auf dynamisch und<br />
partiell rekonfigurierbare Strukturen übertragen.<br />
Als besonders geeignet hat sich hierfür der objektorientierte Entwicklungsansatz [1]<br />
erwiesen.
<strong>1.2</strong>. GARBAGE-COLLECTION 3<br />
<strong>1.2</strong> <strong>Garbage</strong>-<strong>Collection</strong><br />
Im Rahmen dieser Diplomarbeit wird untersucht, wie gut sich die <strong>Garbage</strong>-<strong>Collection</strong> als<br />
wichtiges Element einer objektorientierten Entwurfsmethodik in dynamisch rekonfigurierbare<br />
Hardwarestrukturen übertragen lässt. Unter <strong>Garbage</strong>-<strong>Collection</strong> versteht man<br />
eine automatische Verwaltung von verfügbaren Hardwareressourcen, die durch Rekonfiguration<br />
die vom Benutzer gewünschte Funktionalität annehmen können. Die <strong>Garbage</strong>-<br />
<strong>Collection</strong> soll auf bewährten Konzepten und Algorithmen aus dem Softwareentwurf<br />
basieren und sich in das Gesamtprojekt zur Entwicklung einer objektorientierten Entwurfsmethodik<br />
für Hardware integrieren.<br />
Neben der Analyse allgemeiner Eigenschaften einer <strong>Garbage</strong>-<strong>Collection</strong> für Hardware<br />
soll eine konkrete Implementierung auf FPGAs der Firma Xilinx zur Evaluation dienen.<br />
Außerdem wird untersucht, welche allgemeinen Richtlinien und Eigenschaften sich für<br />
die Konvertierung einer Applikation aus Software in eine hardwarenahe Applikation<br />
finden lassen.<br />
1.3 Überblick<br />
In Kapitel 2 wird vorgestellt, wie sich objektorientierte Konzepte in einen hardwarebasierten<br />
Entwurfsansatz übertragen lassen. Es wird auf die Rahmenbedingungen der<br />
Hardware eingegangen und ein Abstraktionsmodell vorgestellt, das in den Entwurfsmethodiken<br />
verwendet wird.<br />
In Kapitel 3 werden die bestehenden Konzepte und Ansätze zur Speicherverwaltung<br />
analysiert. Ein Schwerpunkt dieser Analyse ist der Mark-Sweep-Algorithmus, der sich<br />
als günstigste Ausgangsbasis für eine <strong>Garbage</strong>-<strong>Collection</strong> in Hardware erwiesen hat.<br />
In Kapitel 4 wird beschrieben, wie sich die einzelnen Aspekte der <strong>Garbage</strong>-<strong>Collection</strong><br />
in Hardware abbilden lassen. Es wird erläutert, welche Konzepte übernommen werden<br />
können und inwieweit sie abgeändert werden müssen, um eine effiziente und kompakte<br />
Hardwareimplementierung zu gestatten. Die dabei entwickelten Algorithmen werden<br />
vorgestellt und erläutert.<br />
Kapitel 5 analysiert die gewonnen Algorithmen. Es wird ein informeller Beweis für die<br />
Korrektheit angegeben und versucht allgemeine Aussagen und Metriken für die Leistungsfähigkeit<br />
einer <strong>Garbage</strong>-<strong>Collection</strong> anzugeben.<br />
Schließlich liefert Kapitel 6 eine kurze Zusammenfassung der Ergebnisse dieser Arbeit<br />
und weist auf offene Fragestellungen und Optimierungspotenziale hin.
4 KAPITEL 1. EINLEITUNG
Kapitel 2<br />
Begriffe und Strukturen<br />
In diesem Kapitel wird die allgemeine Struktur der Hardware vorgestellt, in welcher<br />
die <strong>Garbage</strong>-<strong>Collection</strong> eingebettet ist. Es wird erläutert, wie sich die objektorientierten<br />
Konzepte auf diese Plattform abbilden lassen.<br />
Die existierenden, rekonfigurierbaren Strukturen sind nicht auf die speziellen Eigenschaften<br />
solcher Entwicklungsmethodiken ausgelegt. Um ein System sinnvoller Größe<br />
zu realisieren, ist es deshalb nötig, die Gegebenheiten der vorhandenen Hardware optimal<br />
auszunutzen. Die wesentlichen Teile dieser Konzepte beruhen auf den Arbeiten von<br />
Kühn [2].<br />
2.1 Konfigurationsmöglichkeiten eines FPGA<br />
Als dynamisch rekonfigurierbare Plattform kommt das FPGA Virtex2 Pro V2P30 der<br />
Firma Xilinx zum Einsatz. Die Konfiguration dieser FPGAs basiert auf den Methoden<br />
der klassischen Hardwarewicklung, wie sie auch beim Entwurf von integrierten Schaltungen<br />
zum Einsatz kommen. Zunächst erfolgt ein Logikentwurf in einer abstrakten<br />
Hardwarebeschreibungssprache wie VHDL oder Verilog. Durch Simulation lassen sich<br />
diese Beschreibungen auf hoher Abstraktionsebene evaluieren. Mit Hilfe eines Synthesewerkzeugs<br />
wird aus der Hardwarebeschreibung eine logische Netzliste generiert.<br />
Hierzu kam das Programm Synplify Pro 8.0 der Firma Synplicity [7] zum Einsatz. Diese<br />
Netzliste wird mit herstellerspezifischen Entwicklungstools (Xilinx ISE 6.3 [5]) auf<br />
dem FPGA platziert und verdrahtet. Dabei entsteht ein Bitstream-File. Dieser Bitstream<br />
wird auf das FPGA geladen, das somit konfiguriert ist.<br />
Um nur Teile des FPGAs partiell zu konfigurieren, existieren zwei verschiedene Entwurfswege.<br />
Beide basieren auf dem Entwurfsprozess zur Konfiguration des gesamten<br />
FPGAs. Der “Small Bit Manipulation Design Flow” ermöglicht es, einzelne Logikgatter<br />
neu zu konfigurieren. Großflächige Rekonfigurationen oder Änderungen in der<br />
Verdrahtung einzelner Logikblöcke sind damit nicht möglich. Komplexere Änderun-
6 KAPITEL 2. BEGRIFFE UND STRUKTUREN<br />
gen erlaubt der “Module Based Design Flow”. Dabei werden definierbare Bereiche des<br />
FPGAs zu Modulen zusammengefasst, die sich unabhängig voneinander ersetzen lassen.<br />
Da dieser Design Flow es zulässt, die Verdrahtung zu ändern, muss sichergestellt<br />
werden, dass bei der Rekonfiguration nicht Signalkonflikte an der Schnittstelle mit anderen<br />
Modulen auftreten. Ein Beispiel hierfür wäre eine Signalsenke, die bei der Rekonfiguration<br />
durch ein schreibendes Signal ersetzt wird. Es würde zu einem Konflikt<br />
zweier Treiber auf der Leitung kommen, der einen Hardwaredefekt auslösen kann. Um<br />
in jedem Fall die Signalintegrität zu gewährleisten, werden zur Kommunikation zwischen<br />
den Modulen Bus Macros verwendet. Diese Kommunikationsblöcke bilden die<br />
Schnittstelle zwischen zwei Modulen. Sie nutzen Tri-State-Logikressourcen, um die Signalintegrität<br />
zu gewährleisten. Bus Macros sind während der gesamten Laufzeit des<br />
FPGAs konstant. Eine Kommunikation zweier Module ist ausschließlich über Bus Macros<br />
möglich.<br />
Da der “Small Bit Manipulation Design Flow” kein dynamisches Ändern der Verdrahtung<br />
ermöglicht, ist er für eine Entwurfsmethodik mit hoher Flexibilität als Ziel ungeeignet.<br />
Deshalb wurde für diese Arbeit der “Module Based Design Flow” als Ausgangsbasis<br />
gewählt. Darüber hinaus bietet die eingeschränkte und konstante Schnittstelle eine<br />
direkte Analogie zu den Schnittstellen eines Softwareobjektes, wie in Abschnitt 2.2 beschrieben.<br />
Neben dem FPGA kommt außerdem ein Host-System zum Einsatz, das die erforderlichen<br />
Rekonfigurationen durchführt, das FPGA mit Daten beschickt, sowie die Ergebnisse<br />
ausliest. Die Kommunikation mit dem Host-System erfolgt über eine RS-232<br />
Schnittstelle. Die verwendete FPGA-Plattform AlphaData ADM-XPL [6] würde auch<br />
eine Kommunikation über den schnelleren PCI-Bus gestatten. Auf diese Möglichkeit<br />
wurde verzichtet, da die Pins der entsprechenden Datenleitungen physikalisch weit über<br />
das FPGA verteilt liegen. Eine Schnittstellenerweiterung sämtlicher Module wäre notwendig,<br />
um die Leitungen in einem zentralen Kommunikationsmodul zusammenzuführen.<br />
Um auf diese zusätzliche Komplexität innerhalb der rekonfigurierbaren Module zu<br />
verzichten, wird die RS-232 Schnittstelle bevorzugt, die mit zwei Leitungen (Senden<br />
und Empfangen) zur Kommunikation mit dem Host-System auskommt.<br />
2.2 Objektorientierte Konzepte<br />
Ziel des Projektes, in das diese Arbeit eingebunden ist, ist es zu untersuchen, welche<br />
objektorientierten Konzepte sich auf Hardware abbilden lassen und welche Modifikationen<br />
dabei vorzunehmen sind. Betrand Meyer hat insgesamt 15 Regeln und Prinzipien<br />
aufgestellt, die eine objektorientierte Entwicklung kennzeichnen [1]. Die meisten dieser<br />
Regeln kommen erst bei der Entwicklung eines konkreten Programms zum Einsatz. So<br />
sagt etwa die “Few Interfaces”-Regel aus, dass Objekte so wenig wie möglich miteinander<br />
kommunizieren.
2.2. OBJEKTORIENTIERTE KONZEPTE 7<br />
Einige dieser Prinzipien haben auch Einfluss auf die Konzeption der Laufzeitumgebung,<br />
hier also des FPGAs. Insbesondere gilt:<br />
1. Ein objektorientiertes System besteht aus unabhängigen Objekten.<br />
2. Ein Objekt fasst eine Menge von konzeptionell zusammenhängenden Methoden<br />
zusammen. Diese können über definierte Schnittstellen von anderen Objekten aus<br />
aufgerufen werden<br />
3. Die Schnittstellen eines Objektes werden durch eine Klasse beschrieben. Ein Objekt<br />
ist eine konkrete Instanz dieser Klasse.<br />
4. Die Schnittstellen sollen so kompakt und einfach wie möglich sein.<br />
5. Die Modifikation eines Objektes durch externe Prozesse ist ausschließlich über<br />
seine Schnittstellen möglich.<br />
6. Ein Objekt hat keine Kenntnis über den internen Zustand eines anderen Objekts<br />
(Geheimnisprinzip).<br />
7. Die Objekte werden zur Laufzeit bei Bedarf erzeugt. Zu diesem Zeitpunkt wird<br />
auch der Speicher reserviert, den dieses Objekt benötigt.<br />
8. Wenn ein Objekt von keinem anderen Objekt aus mehr erreichbar ist, sind seine<br />
Daten wegen des Geheimnisprinzips nicht mehr zugänglich. Das Objekt kann<br />
gelöscht, sein Speicherplatz wieder freigegeben werden.<br />
Aus diesen Punkten ergibt sich, weshalb der Ansatz der Objektorientierung sich besonders<br />
gut auf rekonfigurierbare Hardwarestrukturen abbilden lässt: Betrachtet man<br />
die rekonfigurierbaren Module als Objekte eines objektorientierten Systems, so lassen<br />
sich diese Punkte weitgehend direkt abbilden. Der modulare Entwurfsprozess fordert<br />
bereits, dass die Kommunikation zwischen den Modulen ausschließlich über definierte<br />
und kompakte Schnittstellen stattfindet, womit Punkt 4 und 5 erfüllt sind. Kommunikation<br />
außerhalb dieser Schnittstellen ist auf dem FPGA aus physikalischen Gründen nicht<br />
möglich, womit das Geheimnisprinzip aus Punkt 6 erfüllt wird. Das Erzeugen neuer<br />
Objekte und das zugehörige Allozieren von Speicher in Software ist identisch mit dem<br />
dynamischen Rekonfigurieren einzelner Module des FPGAs.<br />
Wie in Punkt 8 der Aufzählung beschrieben, kommt es vor, dass Objekte nicht mehr<br />
benötigt werden und der von ihnen verwendete Speicher freigegeben werden kann. Die<br />
Analogie in dem hardwarebasierten Ansatz ist das Freigeben der Logikressourcen eines<br />
Moduls. In der Praxis wird die Freigabe realisiert, indem solche Module in eine<br />
Liste übernommen werden. Die Liste enthält sämtliche Positionen, die bei der nächsten<br />
entsprechenden Anforderung rekonfiguriert werden können.
8 KAPITEL 2. BEGRIFFE UND STRUKTUREN<br />
Um zu erkennen, welche Objekte nicht mehr benötigt und bei Bedarf gelöscht werden<br />
können, ist es notwendig zu überprüfen, welches Objekt welche anderen Objekte erreichen<br />
kann. Diese Informationen unterliegen als Bestandteil eines Objekts auch dem<br />
Geheimnisprinzip, sind also eigentlich nicht frei von außen abzufragen.<br />
Um trotzdem eine Aussage bezüglich freizugebender Objekte treffen zu können, gibt<br />
es zwei verschiedene Ansätze. Die eine Möglichkeit ist es, das Geheimnisprinzip etwas<br />
abzuschwächen. Jedes Objekt erlaubt dann eine Abfrage, welche anderen Objekte<br />
es noch erreichen kann. Dieser Ansatz ist beispielsweise für Systeme üblich, in denen<br />
Objekte manuell gelöscht werden. Die andere Möglichkeit ist es, die <strong>Garbage</strong>-<br />
<strong>Collection</strong> auf Systemebene zu implementieren. Als Teil der Laufzeitumgebung, die<br />
erst solche Mechanismen wie das Geheimnisprinzip bereitstellt, lassen sich die nötigen<br />
Daten ermitteln, ohne dass eine Implementierung das Geheimnisprinzip verletzen kann.<br />
Es ist üblich, das Erkennen von Objekten, die nicht mehr benötigt werden, zu automatisieren.<br />
Diese Mechanismen zur automatischen Speicherverwaltung werden als<br />
<strong>Garbage</strong>-<strong>Collection</strong> bezeichnet. Diese Arbeit konzentriert sich auf die Entwicklung einer<br />
<strong>Garbage</strong>-<strong>Collection</strong> auf Systemebene. Die Vorteile gegenüber einer Implementierung<br />
auf Anwendungsebene sind zum einen die bessere Effizienz und zum anderen eine<br />
einfache Umgebung für den Entwickler, für den dieser Prozess transparent abläuft.<br />
2.3 Strukturierung der Hardware<br />
Um eine elegante Übertragung der Softwarekonzepte zu gestatten, bietet es sich an,<br />
auf dem FPGA mehrere aufeinander aufbauende Abstraktionsebenen zu definieren, wie<br />
sie in Abbildung 2.1 aufgeführt sind. In den unteren hardwarenahen Ebenen werden die<br />
Mechanismen implementiert, die es erlauben auf den oberen Ebenen vollständig von der<br />
Hardware zu abstrahieren. Die Aufgaben und Bestandteile der einzelnen Layer werden<br />
im Folgenden kurz erläutert.<br />
2.3.1 Layer 1: Logik-Ebene<br />
Die Logik-Ebene stellt die Hardwareressourcen zur Verfügung. Wie erläutert, ist hier<br />
eine Architektur erforderlich, die eine partielle, dynamische Rekonfiguration der Ressourcen<br />
ermöglicht.<br />
In dieser Arbeit kommt ein FPGA der Firma Xilinx zum Einsatz (V2P30 [3]). Die<br />
Virtex2 Reihe bietet als leistungsfähige Baureihe der Firma Xilinx die nötigen Ressourcen,<br />
um verschiedene Ansätze zu evaluieren. Basiseinheiten der Logikressourcen<br />
auf diesem FPGA sind frei konfigurierbare CLBs (Configurable Logic Block), die aus<br />
vier Eingangssignalen ein Ausgangssignal mit Hilfe einer Boole’schen Wahrheitstabelle<br />
bilden. Für taktsynchrone Designs befindet sich hinter jedem CLB ein FlipFlop, das
2.3. STRUKTURIERUNG DER HARDWARE 9<br />
Layer 6<br />
Layer 5<br />
Layer 4<br />
Layer 3<br />
Layer 2<br />
Layer 1<br />
Engineering<br />
Application<br />
Middleware<br />
Protokolle<br />
Structured Hardware<br />
Unstructured Hardware<br />
Abstraktionsgrad<br />
Abbildung 2.1: Abstraktionsebenen der Entwurfsmethodik<br />
optional verwendet werden kann. Die Verbindung der CLBs ist über Verdrahtungsressourcen<br />
realisiert, die sich ebenfalls konfigurieren lassen. Zusätzlich bietet die Virtex-2<br />
Plattform komplexere logische Einheiten an. Dies sind zum Beispiel dedizierte Multiplizierer,<br />
Frequenzvervielfacher und sogar PowerPC-Cores. Auf den Einsatz der komplexeren<br />
Einheiten wird in dieser Arbeit verzichtet, um den vorgestellten Entwurfsansatz so<br />
flexibel wie möglich zu halten. Partielle Rekonfiguration ist erst seit kurzem verfügbar<br />
und nur innerhalb gewisser Randbedingungen möglich [4], auf die im nächsten Layer<br />
eingegangen wird.<br />
2.3.2 Layer 2: Struktur-Ebene<br />
In der Struktur-Ebene werden die Logikressourcen von Layer 1 in passende rekonfigurierbare<br />
Module unterteilt und die Busstruktur zur Kommunikation festgelegt. Die<br />
Randbedingungen der verwendeten Hardwarearchitektur bestimmen dieses Design maßgeblich.<br />
Xilinx-FPGAs besitzen eine rechteckige Struktur von ungefähr 92*160 Elementen<br />
(die genauen Maße unterscheiden sich je nach verwendetem Typ). Zur Orientierung<br />
verwendet Xilinx ein X-Y-Koordinaten-System wie in Abbildung 2.2 dargestellt.<br />
Rekonfigurierbare Module müssen die vollständige Höhe des FPGAs abdecken und besitzen<br />
eine horizontale Breite, die ein Vielfaches von Vier darstellen muss. Ein FPGA<br />
mit 92*160 Elementen besitzt damit maximal 40 rekonfigurierbare Elemente der Größe<br />
92*4 Zellen.<br />
Wie bereits erwähnt, muss die Kommunikation zwischen den rekonfigurierbaren Blöcken<br />
über dedizierte Routingressourcen erfolgen. Um die Signalintegrität an der Schnittstelle<br />
auch während der Rekonfiguration eines Moduls sicherzustellen, müssen zur Kom-
10 KAPITEL 2. BEGRIFFE UND STRUKTUREN<br />
Y<br />
n<br />
rekonf.<br />
Modul<br />
3<br />
rekonf.<br />
Modul<br />
2<br />
rekonf.<br />
Modul<br />
1<br />
rekonf.<br />
Modul<br />
0<br />
Arbiter<br />
X<br />
Abbildung 2.2: Struktur eines Virtex2 Pro FPGA<br />
munikation Bussysteme mit Tri-State-Treibern verwendet werden. Die Anzahl der Tri-<br />
State-Treiber ist eng begrenzt und limitiert die maximal mögliche Schnittstelle zwischen<br />
jeweils zwei nebeneinanderliegenden Modulen.<br />
Deshalb ist zur Kommunikation der Module untereinander ein Bussystem realisiert,<br />
welches gegenüber einer direkten Punkt-zu-Punkt Verbindung aller Module auf Kosten<br />
der Geschwindigkeit mit weniger Verdrahtungsressourcen auskommt. Insgesamt verwendet<br />
die Implementierung einen 40 Bit breiten Datenbus.<br />
Neben den frei konfigurierbaren Logikblöcken ist mit dem Arbiter ein statisches Modul<br />
implementiert. Der Arbiter bildet die Laufzeitumgebung für ein Programm, das in<br />
den Modulen realisiert ist. Er fungiert als Busmaster und bildet das Interface zum Host-<br />
System.<br />
Die aktuelle Implementierung besitzt zur Evaluation 15 frei konfigurierbare Module.
2.3. STRUKTURIERUNG DER HARDWARE 11<br />
2.3.3 Layer 3: Protokolle<br />
Die komplexen Busstrukturen machen fortgeschrittene Protokolle erforderlich, die als<br />
eigene Abstraktionsebene aufgefasst werden. Im Rahmen dieser Protokolle wird jedem<br />
rekonfigurierbaren Modul eine Adresse zugeteilt, die mit ihrer geometrischen Position<br />
auf dem Chip identisch ist. Das Modul 3 liegt also beispielsweise zwischen den Modulen<br />
4 und 5. Module sind von rechts nach links durchnummeriert, der Arbiter besitzt die<br />
Adresse 0. In Abbildung 2.2 sind die Adressen über den einzelnen Spalten angegeben.<br />
Aktuell besitzen die Adressen eine Breite von vier Bit, die ausreicht, bis zu 15 Module<br />
anzusprechen.<br />
Die Kommunikation über den Bus enthält damit immer die Adresse des Zielmoduls.<br />
Dazu wird optional eine Absenderadresse mitgeschickt, um beispielsweise einen Rückgabewert<br />
wieder zurück zu senden. Im Normalfall besitzt ein Objekt mehrere unterschiedliche<br />
Methoden, die von außen zugänglich sind. Alle Methoden eines Objekts<br />
besitzen eine eindeutige Identifikationsnummer (ID), die beim Aufruf ebenfalls übertragen<br />
wird. Schließlich müssen auch noch die Argumente des Prozeduraufrufs übertragen<br />
werden.<br />
Die Adresse des Ziels, sowie die Absenderadresse sind jeweils vier Bit breit. Ebenso<br />
das Signal zur Auswählen der Methode, womit ein Objekt maximal 16 verschiedene<br />
Methoden besitzen kann. Die Argumente sind als 16 Bit breiter Bus realisiert.<br />
Zusammengefasst gilt also, dass bei jeder Kommunikation die Adresse des Zielblocks,<br />
optional eine Absenderadresse, Daten für einen Prozeduraufruf, und die ID der aufgerufenen<br />
Prozedur versendet werden. Zusätzliche Kommunikationsstrukturen für Reset-<br />
Signale und ähnliches sind ebenfalls erforderlich.<br />
Außerdem sind verschiedene weitere Kommunikationsstrukturen implementiert, die<br />
beispielsweise ein globales Reset-Signal zu allen Komponenten leiten. Die Möglichkeit,<br />
eine zusätzliche Leitung für die <strong>Garbage</strong>-<strong>Collection</strong> zu verwenden ist gegeben. In<br />
dieser Arbeit werden zwei weitere globale Signale eingeführt, die in Kapitel 4 vorgestellt<br />
werden.<br />
2.3.4 Layer 4: Middleware<br />
Die Kombination aus den rekonfigurierbaren Modulen und den Mechanismen zur Protokollverwaltung<br />
bildet den Middleware-Layer.<br />
Wichtigstes Element der Middleware sind die ComputingPages, die einen Containertyp<br />
für die darin eingebetteten eigentlichen Objekte bilden. Abbildung 2.3 gibt einen<br />
schematischen Überblick über die Einbettung der Objekte. Die ComputingPages bieten<br />
ein abstraktes und vereinfachtes Interface zu den Objekten an. Sie implementieren erforderliche<br />
Verwaltungsmechanismen wie Adressdekodierung für den Bus, und Prozedurdekodierung.<br />
Auch Teile der <strong>Garbage</strong>-<strong>Collection</strong>, die als Teil jedes Objekts realisiert<br />
sind, sind auf diesem Layer angesiedelt.
12 KAPITEL 2. BEGRIFFE UND STRUKTUREN<br />
Computing Page<br />
RightIn<br />
LeftOut<br />
Protokoll Decoder<br />
RightOut<br />
LeftIn<br />
Objekt<br />
Adresse : 4 Bit<br />
Funktion 0(Arg:16 Bit)<br />
Funktion 1(Arg:16 Bit)<br />
Funktion 15(Arg:16 Bit)<br />
Abbildung 2.3: Schematischer Aufbau eines Objekts und der Computing Page<br />
Darüber hinaus ist der Arbiter ein Teil der Middleware. Er steuert als Busmaster die<br />
Kommunikation zwischen den verschiedenen ComputingPages. Der Arbiter verwaltet<br />
die freien ComputingPages und deaktiviert das Bussystem für die Rekonfiguration einer<br />
ComputingPage. Die systemweiten Komponenten der <strong>Garbage</strong>-<strong>Collection</strong> sind ebenfalls<br />
als Teil des Arbiters implementiert. Im Sinne der Kommunikationsprotokolle aus<br />
dem Protokoll-Layer (Layer 3) besitzt der Arbiter die Adresse 0. Geometrisch ist er auf<br />
dem Chip am rechten Rand angeordnet. Eine Rekonfiguration zur Laufzeit ist für den<br />
Arbiter nicht vorgesehen.<br />
2.3.5 Layer 5: Applikation<br />
Die Applikations-Ebene kann auch als Anwendungsebene bezeichnet werden. Sie ist<br />
die Basis zur Entwicklung von Programmen, während die darunterliegenden Layer für<br />
einen Anwender weitgehend transparent und unzugänglich sind. Der Applikations-Layer<br />
belegt den rekonfigurierbaren Block einer ComputingPages mit individuellen Funktionsblöcken.<br />
Jede aktive ComputingPage bildet ein Objekt einer Klasse.<br />
2.3.6 Layer 6: SW-Engineering<br />
Durch Verknüpfung der einzelnen Objekte untereinander entstehen vollständige Threads<br />
und Programme. Die Verknüpfung der Objekte untereinander folgt dabei dem objektorientierten<br />
Paradigma der Modularisierung. Das heißt, die Objekte können einzig über<br />
Prozeduraufrufe auf andere Objekte zugreifen. Dazu muss das aufrufende Objekt die<br />
Adresse des Zielobjektes kennen. Ein Objekt kann durch Aufrufen eines Konstruktors
2.4. BEISPIEL 13<br />
die Anzahl der Objekte im Gesamtsystem erhöhen. Zum Start eines Programms wird ein<br />
initiales Objekt vom Host-System aus erstellt. Diese Objekte werden als Wurzelknoten<br />
bezeichnet und leben während der gesamten Programmausführung. Es ist ohne weiteres<br />
möglich mehrere Wurzelknoten und damit mehrere Threads oder Programme parallel<br />
auf dem FPGA ablaufen zu lassen.<br />
2.4 Beispiel<br />
Die Struktur des Anwendungessystems soll an einem Beispiel verdeutlicht werden. Abbildung<br />
2.4 zeigt das Programmablaufdiagramm für folgendes Codesegment:<br />
c l a s s a : Root {<br />
. . .<br />
Math m = new Math ( ) ;<br />
m. run ( ) ;<br />
P r i n t p = new P r i n t ( ) ;<br />
p . p r i n t (m) ;<br />
}<br />
c l a s s S q r t {<br />
i n t run ( i n t Op1 ) {<br />
return s q r t ( Op1 ) ;<br />
}<br />
c l a s s Math {<br />
void run ( ) {<br />
S q r t s = new S q r t ( ) ;<br />
r e s = s . g e t S q r t ( 3 . 1 4 1 ) ;<br />
s=NULL;<br />
return ( ) ;<br />
}<br />
}<br />
c l a s s P r i n t {<br />
p r i n t ( C l a s s a ) {<br />
. . .<br />
}<br />
}<br />
Das Beispiel beginnt während der Ausführung von Objekt “a” der Klasse “Root”.<br />
Man beachte, dass die Freigabe des Objektes “s” der Klasse “Sqrt” zu keinem vorher<br />
festgelegten Zeitpunkt erfolgt. Der Zeitpunkt wird durch die nächste, der Zuweisung<br />
“s = NULL” folgende, Aktivierung der <strong>Garbage</strong>-<strong>Collection</strong> bestimmt. Sobald mit<br />
“s = NULL” die Referenz auf das Objekt der Klasse “Sqrt” überschrieben wurde,<br />
ist dieses nicht mehr erreichbar und kann freigegeben werden.<br />
2.5 Vereinfachte Repräsentation eines Programms<br />
Für die <strong>Garbage</strong>-<strong>Collection</strong> ist die Verknüpfung zwischen den Objekten von besonderer<br />
Bedeutung, weil sie angibt, welche Objekte nicht mehr erreichbar sind und somit freigegeben<br />
werden können. Zwei Objekte sind dann miteinander verknüpft, wenn ein Objekt<br />
auf die Methoden eines anderen Objektes zugreifen kann. Hinreichende Bedingung für<br />
die Verknüpfung ist es, wenn ein Objekt A die Adresse eines anderen Objektes B kennt.<br />
Für eine Untersuchung der Eigenschaften eines <strong>Garbage</strong>-<strong>Collection</strong>-Algorithmus bietet<br />
es sich an, vom gegebenen Programm zu einem gerichteten Objektgraphen zu abstrahieren.<br />
Jeder Knoten des Graphen repräsentiert ein Objekt und die ausgehenden gerichteten<br />
Kanten geben an, welche anderen Objekte von ihm aus über ihre Adressen
14 KAPITEL 2. BEGRIFFE UND STRUKTUREN<br />
erreichbar sind. Der Graph kann über mehrere ausgezeichnete Wurzelknoten verfügen,<br />
die jeweils die Wurzel eines Threads repräsentieren.<br />
Der Objektgraph für das Beispiel aus Abschnitt 2.4 ist in Abbildung 2.4 dargestellt.<br />
Man sieht, dass verschiedene Operationen, wie Konstruktoraufrufe und lokale Variablenzuweisungen<br />
(“s = NULL”) den Objektgraphen verändern.<br />
Der Graph abstrahiert von der Funktionalität der Objekte, die für eine <strong>Garbage</strong>-<strong>Collection</strong><br />
unerheblich ist. Er beschränkt sich auf die Darstellung der Erreichbarkeit der<br />
Objekte. Die Wurzelknoten eines Programms, die im FPGA von extern erstellt werden,<br />
bilden auch die Wurzelknoten des Graphen.<br />
Im allgemeinen Fall können keine weiteren Aussagen bezüglich der Graphenstruktur<br />
gemacht werden. Trotzdem lassen sich einige zusätzliche heuristische Annahmen<br />
treffen, die darauf beruhen, dass Programme üblicherweise nach gewissen Regeln implementiert<br />
werden. In den meisten Systemen existieren langlebige Objekte, in denen<br />
für kurze Zeit temporäre Objekte angelegt werden. Diese temporären Objekte werden<br />
nur im Rahmen einer Operation benötigt und danach wieder gelöscht. Außerdem hat<br />
sich herausgestellt, dass ein Objekt meist mindestens ebensolang existiert, wie weitere<br />
von ihm erstellte Objekte (Kindobjekte).<br />
Dadurch entsteht eine hierarchische Objektstruktur, die einem Baum der Graphentheorie<br />
ähnelt. Die Tiefe eines Knotens, die als die Anzahl der Kanten zwischen einer<br />
Wurzel und dem Knoten definiert ist, korrespondiert mit seiner Lebensdauer. Die Blätter<br />
des Baums besitzen die höchste Tiefe und sind die kurzlebigsten Objekte. Sie werden<br />
als erstes wieder gelöscht.<br />
Trotzdem bleibt die Baumstruktur nur eine näherungsweise Beschreibung für die meisten<br />
Objektgraphen. Referenzen, die diese Baumstruktur zerstören, kommen ebenfalls<br />
vor, sind jedoch gegenüber den regulären Kanten, eher selten. Für die Entwicklung einer<br />
<strong>Garbage</strong>-<strong>Collection</strong> ist diese vereinfachte Sicht damit zumeist unzulänglich. Sie eignet<br />
sich jedoch gut für statistische Analysen der <strong>Garbage</strong>-<strong>Collection</strong>. Hierzu zählen etwa<br />
Aussagen über die Effizienz oder der Versuch, einen günstigen Zeitpunkt zum Start der<br />
<strong>Garbage</strong>-<strong>Collection</strong> zu finden.<br />
Es gibt verschiedene Untersuchungen, die versuchen, zur Optimierung der <strong>Garbage</strong>-<br />
<strong>Collection</strong> weitere Aussagen über die Struktur des Graphen zu treffen und von ihnen<br />
zu profitieren. Einige für diese Arbeit relevante Untersuchungen werden in Kapitel 5<br />
vorgestellt.
2.5. VEREINFACHTE REPRÄSENTATION EINES PROGRAMMS 15<br />
Instanzierung<br />
Procedure(Source,Target [,Operands,...])<br />
Dispatcher / Arbiter a : Root m : Math s : Sqrt p : Print<br />
Create(Math)<br />
Constructor(0,m)<br />
a<br />
Return(a,m)<br />
run(a,m)<br />
run(a,m)<br />
m<br />
a<br />
Create(Sqrt)<br />
Constructor(0,s)<br />
Return(m,s)<br />
m<br />
a<br />
getSqrt(m,s,"3.141")<br />
getSqrt(m,s,"3.141")<br />
Return(m,"1.77")<br />
Return(m,"1.77")<br />
Return(a)<br />
Return(a)<br />
m<br />
s<br />
a<br />
Create(Print)<br />
Constructor(0,p)<br />
Return(a,p)<br />
m<br />
s<br />
a<br />
print(a,p,"1.77")<br />
print(a,p,"1.77")<br />
m<br />
a<br />
p<br />
Abbildung 2.4: Programmablauf und Objektgraph
16 KAPITEL 2. BEGRIFFE UND STRUKTUREN
Kapitel 3<br />
<strong>Garbage</strong>-<strong>Collection</strong> in Software<br />
Mit dem Begriff <strong>Garbage</strong>-<strong>Collection</strong> (GC) bezeichnet man die automatische Speicherverwaltung<br />
eines objektorientierten Systems. Aufgabe der GC ist es, Objekte, die nicht<br />
mehr verwendet werden können, zu löschen und ihren Speicherbereich zur Wiederverwendung<br />
freizugeben. Ein Objekt kann dann nicht mehr verwendet werden, wenn es<br />
in anderen Objekte keine Referenzen auf dieses Objekt mehr gibt, es also nicht mehr<br />
erreichbar ist. Meyer [1] bietet einen kurzen Überblick über die Entwicklung der GC<br />
und ihre Verwendung in objektorientierten Systemen.<br />
Man unterscheidet zwischen zwei Prozessen: Der Mutator verändert den Inhalt des<br />
Speichers. Er enthält das eigentliche Programm und sollte idealerweise ohne Unterbrechung<br />
laufen. Der Collector implementiert die <strong>Garbage</strong>-<strong>Collection</strong>. Seine Aufgabe ist<br />
es zu gewährleisten, dass der Mutator immer in der Lage ist, ausreichend Speicher zu allozieren.<br />
Der Collector sollte den Mutator so wenig wie möglich behindern, ohne dabei<br />
seine Funktionalität einzubüßen.<br />
Die Funktionalität einer GC lässt sich in zwei Anforderung aufteilen:<br />
Definition 3.0.1 (Korrektheit)<br />
Jedes Objekt, das von der <strong>Garbage</strong>-<strong>Collection</strong> gelöscht wird, ist zuvor von keinem anderen<br />
Objekt aus mehr erreichbar.<br />
Definition 3.0.2 (Vollständigkeit)<br />
Alle nicht mehr erreichbaren Objekte werden erkannt und freigegeben.<br />
Die Bedingung der Vollständigkeit wird oft zugunsten einer effizienteren GC-Implementierung<br />
abgeschwächt. Mögliche Neuformulierungen der Vollständigkeit fordern<br />
dann, dass jedes nicht erreichbare Objekt irgendwann einmal gelöscht wird. Es wird<br />
nicht mehr gefordert, dass dies bereits beim nächsten GC-Lauf stattfindet. Eine andere<br />
Möglichkeit zur Abschwächung ist die Forderung, dass die GC in jedem Schritt ein<br />
garantiertes Minimum an nicht mehr erreichbaren Objekten löscht. Wichtig ist es, die
18 KAPITEL 3. GARBAGE-COLLECTION IN SOFTWARE<br />
Anforderungen des Mutators zum Allozieren neuer Objekte erfüllen zu können, solange<br />
Speicherbereiche existieren, die nicht mit aktiven Objekten belegt sind.<br />
Wie in Kapitel 2 erläutert, lassen sich die ComputingPages als Objekte einer objektorientierten<br />
Entwurfsmethodik interpretieren.<br />
Der Analogie folgend bildet das rekonfigurierbare FPGA den Speicher, in dem diese<br />
Objekte abgelegt sind. Auch die Konzepte der <strong>Garbage</strong>-<strong>Collection</strong> lassen sich mit dieser<br />
Analogie auf FPGAs übertragen. Ihre Aufgabe ist es, die verfügbaren rekonfigurierbaren<br />
Ressourcen zu verwalten.<br />
Neben der Korrektheit und Vollständigkeit ist das wesentliche Merkmal eines guten<br />
Algorithmus zur GC, dass das Programm bei seiner Ausführung so kurz wie möglich unterbrochen<br />
wird. Das Instantiieren eines neuen Objektes (die Anforderung eines freien<br />
Speicherbereiches) soll möglichst unterbrechungsfrei stattfinden. Dazu sind verschiedene<br />
Ansätze zur <strong>Garbage</strong>-<strong>Collection</strong> entwickelt worden, die im Folgenden kurz vorgestellt<br />
werden. Zusätzlich wird die Relevanz der Ansätze für rekonfigurierbare Hardware<br />
diskutiert, bevor in Kapitel 4 ein Ansatz für eine effiziente <strong>Garbage</strong>-<strong>Collection</strong> in rekonfigurierbarer<br />
Hardware entwickelt und in Kapitel 5 genauer untersucht wird.<br />
3.1 Statische Reservierung<br />
Bei der statischen Speicherreservierung gibt es keine eigentliche <strong>Garbage</strong>-<strong>Collection</strong>.<br />
Trotzdem soll dieses Prinzip hier der Vollständigkeit halber erwähnt werden. Bei dieser<br />
Art der Speicherverwaltung wird bereits beim Programmstart der gesamte benötigte<br />
Speicher reserviert. Dynamische Programme, bei denen der Speicherbedarf zur Laufzeit<br />
wachsen kann, sind damit nicht möglich. Vorteil dieser Art von Speicherverwaltung<br />
ist ihre Einfachheit: Man benötigt keine <strong>Garbage</strong>-<strong>Collection</strong> und kann daher harten<br />
Echtzeit-Anforderungen genügen, die man durch externe Speicherverwaltung nicht<br />
mehr erfüllen kann. Diese Art der Speicherverwaltung entspricht am ehesten dem klassischen<br />
Ansatz zur Entwicklung von Hardwaredesigns und ist häufig in “Embedded<br />
Systems” anzutreffen .<br />
Charakteristika einer statischen Reservierung<br />
• Keine dynamische Speicherreservierung und Freigabe<br />
• Sehr einfach<br />
• Erfüllt harte Echtzeit-Anforderung<br />
• Keine dynamischen Programme möglich<br />
• Analogie zur Klassischer Hardwareentwicklung
3.2. EXPLIZITE RESERVIERUNG 19<br />
3.2 Explizite Reservierung<br />
Bei der expliziten Speicherreservierung werden die Aufgaben der GC vom Programmierer<br />
übernommen. Dieser kann durch Kenntnis des Programmablaufs bestimmen, wann<br />
ein Objekt nicht mehr verwendet wird. Daraufhin gibt er den vorher vom Objekt belegten<br />
Speicher mit einem entsprechenden Befehl explizit frei. Bekanntestes Beispiel für<br />
dieses Konzept ist die Sprache C [8] mit den Befehlen malloc() und free(). In der Hardwareentwicklung<br />
entspricht es am ehesten den bestehenden Ansätzen zur Ausnutzung<br />
von dynamisch rekonfigurierbarer Hardware, bei denen ebenfalls der Entwickler die<br />
Rekonfiguration steuert. Vorteil dieser Variante ist die gute Kontrolle über das Zeitverhalten<br />
der Speicherverwaltung. Ein wesentlicher Nachteil ist die Komplexität der Softwareentwicklung.<br />
In großen Projekten ist es oft sehr schwer, den Überblick über die<br />
instantiierten Objekte zu behalten. Vergisst der Entwickler Objekte freizugeben, kann<br />
dies zu schwer nachvollziehbaren Fehlern (Memory Leaks) führen, die ein typisches<br />
Problem vieler Anwendungen bilden, die diese Art der Speicherverwaltung nutzen.<br />
Charakteristika einer expliziten Reservierung<br />
• Beispiel: “free” und “malloc” aus C<br />
• Einfach zu implementieren<br />
• Teilweise deterministisches Timing (in kritischen Teilen kein “free” verwenden)<br />
• Analogie zur normalen Hardware-Rekonfiguration<br />
3.3 Referenzzählung<br />
Die Referenzzählung [9] war der erste automatische Ansatz zur <strong>Garbage</strong>-<strong>Collection</strong>. Zu<br />
jedem Objekt wird ein Zähler mitgeführt, der angibt, wieviele Referenzen noch auf dieses<br />
Objekt verweisen. Wenn der Zähler auf Null geht, gibt es keine Referenzen mehr<br />
und das Objekt kann gelöscht werden. Um den Referenzzähler aktuell zu halten, ist<br />
es erforderlich, bei jeder Zuweisung einer Referenz (oder auch deren Löschen) den<br />
Referenzzähler zu inkrementieren und dekrementieren. Damit ist dieser Ansatz relativ<br />
zeitaufwändig. Vorteil des Ansatzes ist, dass sich der Mehraufwand gleichmäßig<br />
über das Programm verteilt und die Latenz einer Operation gut einschätzbar ist. Der<br />
Ansatz ermöglicht also die Einhaltung von weichen Echtzeitbedingungen. Als wesentlicher<br />
Nachteil bleibt zu erwähnen, dass die Referenzzählung nicht in der Lage ist eine<br />
zyklisch verkettete Liste korrekt als <strong>Garbage</strong> zu erkennen, weil alle diese Objekte noch<br />
einen Referenzähler > 0 besitzen.
20 KAPITEL 3. GARBAGE-COLLECTION IN SOFTWARE<br />
Charakteristika der Referenzzählung<br />
• Einfacher Ansatz<br />
• Kann keine Zyklen erkennen<br />
• Bei jeder Zuweisung ändert sich die Referenz (Häufiger Aufruf)<br />
• Weiche Echtzeitbedingungen<br />
3.4 Mark-Sweep <strong>Collection</strong><br />
Das Verfahren der Mark-Sweep-<strong>Collection</strong> ist heute das am häufigsten verwendete Verfahren<br />
zur automatisierten <strong>Garbage</strong>-<strong>Collection</strong>. Der Begriff Mark-Sweep-<strong>Collection</strong> wird<br />
in viele Arbeiten synonym mit dem Begriff <strong>Garbage</strong>-<strong>Collection</strong> verwendet.<br />
Bei diesem Verfahren arbeitet der Collector-Prozess in zwei Phasen: In der ersten Phase<br />
(Mark) werden ausgehend von der Wurzel alle erreichbaren Knoten (die Objekte)<br />
markiert. In der zweiten Phase wird der gesamte Speicher einmal linear durchlaufen<br />
(Sweep). Objekte, die nach diesen beiden Phasen nicht als erreichbar markiert sind,<br />
werden wieder freigegeben.<br />
Gegenüber der Referenzzählung gibt es bei diesem Verfahren keine Probleme mit isolierten<br />
Zyklen, weil sie ausgehend von der Wurzel aus nicht erreichbar sind und somit<br />
während der Mark-Phase auch nicht markiert werden.<br />
Das Verfahren ermöglicht es, ausgehend von einem beliebigen Knoten, kleinere Teilbäume<br />
zu traversieren. Damit kann man ohne den Aufwand einer vollständigen Traversierung<br />
nicht mehr erreichbare Objekte finden und freigeben. Diese Idee ist unter dem<br />
Begriff Generational <strong>Collection</strong> bekannt und wird in Kapitel 3.5.3 näher erläutert.<br />
Hauptnachteil der Mark-Sweep-<strong>Collection</strong> ist ihr schlechtes Echtzeitverhalten. Sie erfordert<br />
eine vollständige Traversierung des Objekt-Graphen im Mark-Prozess und eine<br />
lineare Traversierung des kompletten Speichers im Sweep-Prozess für einen Collector-<br />
Aufruf. Dadurch kann es zu starken Verzögerungen im Ablauf des Mutators kommen.<br />
Ein weiterer Nachteil dieser Art von <strong>Garbage</strong>-<strong>Collection</strong>ist die Fragmentierung des<br />
Speichers. Durch das Löschen von Objekten inmitten des linear angeordneten Speichers<br />
entstehen Lücken. Fordert die nächste Allozierung mehr Speicher an, als in einer<br />
einzelnen Lücke vorhanden ist, muss die Allozierung verhindert werden, obwohl insgesamt<br />
ausreichend freier Speicher vorhanden wäre. Um die Fragmentierung zu beseitigen<br />
muss der belegte Speicher so weit umsortiert werden, dass ausreichend zusammenhängender<br />
freier Speicher vorhanden ist. Eine effiziente Strategie für die Problematik der<br />
Fragmentierung zu finden, ist eine weitere Aufgabe eines GC-Algorithmus.<br />
In der Praxis hat sich die Mark-Sweep-<strong>Collection</strong> durchgesetzt. Grund dafür ist, neben<br />
der einfachen Implementierung, die gute Erweiterbarkeit um die Nachteile des Verfahrens<br />
zu vermindern oder zu beseitigen.
3.5. ERWEITERUNGEN DER MARK-SWEEP-COLLECTION 21<br />
Auch für Hardware bietet sich die Mark-Sweep-<strong>Collection</strong> an. Sie ermöglicht Erweiterungen<br />
und Mechanismen zur Nebenläufigkeit, die sich besonders gut für eine direkte<br />
Abbildung auf die inhärent nebenläufige Hardwareumgebung eignen. Das Problem der<br />
Fragmentierung ist aufgrund der homogenen Struktur der ComputingPages vernachlässigbar<br />
für unsere Implementierung vernachlässigbar.<br />
Charakteristika der Mark-Sweep-<strong>Collection</strong><br />
• Auch bekannt als <strong>Garbage</strong>-<strong>Collection</strong><br />
• Idee: Objekte bilden einen Baum, der traversiert wird<br />
• Findet auch isolierte Zyklen<br />
• Fragmentierung des Speichers<br />
• Ungünstiges Echtzeit-Verhalten<br />
• Normalerweise suspendiert der Prozess während dem Mark-Schritt<br />
• Zum nebenläufigen Algorithmus erweiterbar<br />
3.5 Erweiterungen der Mark-Sweep-<strong>Collection</strong><br />
Im Folgenden werden die Konzepte der wichtigsten Erweiterungen für die Mark-Sweep-<br />
<strong>Collection</strong> vorgestellt. Es wird zusätzlich jeweils kurz erwähnt, inwiefern sie für eine<br />
Hardwareimplementierung von Interesse sein könnten.<br />
3.5.1 Fragmentierung<br />
Die Copying <strong>Collection</strong> [10] ist ein Ansatz, das Problem der Fragmentierung des Speichers<br />
zu beseitigen. Bei dieser Art der <strong>Garbage</strong>-<strong>Collection</strong> werden zwei disjunkte, gleich<br />
große Speicherbereiche für das Programm (den Mutator) reserviert. Alle Objekte liegen<br />
gemeinsam in einem der beiden Speicherbereiche, der im Lauf der Programmlaufzeit<br />
fragmentiert. Während des GC-Zyklus werden alle aktiven Objekte in den anderen Bereich<br />
verschoben und dort wieder hintereinander im Speicher abgelegt. Danach wechseln<br />
die beiden Speicherbereiche ihre Funktion und der Speicher wird durch den Mutator<br />
bis zum nächste GC-Lauf wieder fragmentiert.<br />
Für die Hardwareimplementierung ist das Problem der Fragmentierung vernachlässigbar,<br />
da die technischen Rahmenbedingungen nur eine kleine Zahl gleichförmig großer<br />
ComputingPages zulassen.
22 KAPITEL 3. GARBAGE-COLLECTION IN SOFTWARE<br />
3.5.2 Nebenläufigkeit<br />
Eine wichtige Methode, die schlechten Echtzeitverhältnisse der Mark-Sweep-<strong>Collection</strong><br />
zu verbessern ist, Mutator und Collector als nebenläufige Prozesse zu realisieren. Im<br />
Idealfall kann der Mutator so ohne Unterbrechungen ablaufen, während der Collector<br />
parallel nicht mehr erreichbare Objekte findet und freigibt. Der Mutator muss nur unterbrochen<br />
werden, wenn der Collector nicht ausreichend schnell freie Objekte finden<br />
kann.<br />
Für eine dedizierte Hardwareimplementierung bietet sich ein solcher Ansatz an, da<br />
man die nebenläufigen Strukturen der Plattform optimal nutzen kann.<br />
Die Implementierung eines nebenläufigen Algorithmus ist sehr komplex. Insbesondere<br />
der Beweis der Korrektheit eines solchen Algorithmus erfordert aufwändige formale<br />
Verifikationsmethoden, wie sie zum Beispiel von Dijkstra [11] angewandt werden.<br />
Es lassen sich jedoch einige gemeinsame Aspekte für eine nebenläufige <strong>Garbage</strong>-<br />
<strong>Collection</strong> finden, die ein korrekter Algorithmus aufweisen muss. Huelsbergen [12],<br />
Ben-Ari [13], Doligez [14] und Domani [15] geben einige Beispiele für solche Algorithmen<br />
zusammen mit einem formalen oder semi-formalen Beweis ihrer Korrektheit.<br />
Für einen solchen Beweis muss man über atomare Operationen verfügen, die im Rahmen<br />
des GC-Algorithmus zum Einsatz kommen. Ohne solche Operationen lassen sich<br />
keine Aussagen und Invarianten über die Funktionalität eines nebenläufigen Algorithmus<br />
bilden. Für eine Hardwareimplementierung bietet das synchrone Systemdesign und<br />
das Kommunikationsprotokoll über den Arbiter eine passende Ausgangsbasis für atomare<br />
Operationen der <strong>Garbage</strong>-<strong>Collection</strong>.<br />
Neben atomaren Operationen muss man darüber hinaus die Funktionalität des Mutators<br />
während eines aktiven Collector-Zyklus in gewissem Rahmen einschränken. Normalerweise<br />
schränkt man entweder die Möglichkeiten zum Schreiben in den Speicher<br />
(write-barrier) oder das Lesen aus dem Speicher (read-barrier) ein. Ohne diese Einschränkungen<br />
könnte der Mutator durch Änderungen an der Objektstruktur während<br />
des aktiven Mark-Prozess die Korrektheit des Algorithmus verletzen. Abbildung 3.1<br />
illustriert dieses Problem an einem Beispiel. Die Zahlen geben die Reihenfolge der Traversierung<br />
während des Mark-Schrittes an, wobei von a) nach b) im Mutator eine Kante<br />
gelöscht und von b) nach c) eine neue Kante hinzugefügt wird. Der Algorithmus erkennt<br />
den Knoten “X” nicht als aktives Objekt, obwohl es offensichtlich erreichbar ist.<br />
Darüber hinaus bilden diese Barrieren die Basis für formelle Beweise zur Korrektheit<br />
einer nebenläufigen <strong>Garbage</strong>-<strong>Collection</strong>.<br />
Ein weiterer Nachteil der nebenläufigen Algorithmen bleibt neben ihrer höheren Komplexität<br />
gegenüber sequenziellen Verfahren, dass man bei ihnen die Vollständigkeit der<br />
<strong>Garbage</strong>-<strong>Collection</strong> aufgeben muss. Da der Mutator während der Mark-Traversierung<br />
weiter auf die Objektstruktur Zugriff hat, kann es dazu kommen, dass ein Objekte noch<br />
als erreichbar gekennzeichnet ist, obwohl es noch vor dem Ende der Traversierung durch<br />
den Mutator gelöscht wird. Solche Objekte werden im nächsten GC-Zyklus erkannt.
3.5. ERWEITERUNGEN DER MARK-SWEEP-COLLECTION 23<br />
1<br />
1<br />
1<br />
2 2 5<br />
2<br />
5<br />
3 4<br />
3 4 6<br />
X<br />
a) b) c)<br />
Abbildung 3.1: Inkorrekter Objektgraph bei nebenläufiger <strong>Garbage</strong>-<strong>Collection</strong><br />
Eine Erweiterung der Barrieren, um auch diese Problem-Konstellationen zu vermeiden,<br />
würde wieder direkt zu einem sequenziellen Algorithmus zurückführen.<br />
Üblicherweise nimmt man den Nachteil der nicht mehr erfüllbaren Vollständigkeit in<br />
Kauf, da man in den meisten Szenarien nicht in einem GC-Zyklus sämtlichen freizugebenden<br />
Speicher benötigt, sondern nur ausreichend viel freien Speicher, um bis zum<br />
übernächsten GC-Zyklus neue Objekte allozieren zu können.<br />
3.5.3 Generational <strong>Collection</strong><br />
Ein weiterer Ansatz, das schlechte Echtzeit-Verhalten der Mark-Sweep-<strong>Collection</strong> zu<br />
verbessern, ist die Generational <strong>Collection</strong>, die zuerst von Lieberman [18] vorgeschlagen<br />
wurde und auch unter dem Begriff Generation Scaveging bekannt ist. Dabei wird<br />
der Objektgraph in verschiedene Objekt-Klassen partitioniert. Die Klassen sind dabei<br />
über die Lebensdauer ihrer Objekte definiert.<br />
Der Mark-Sweep Prozess findet nun innerhalb der einzelnen Partitionen statt, wobei<br />
die Partition mit den kurzlebigeren Objekten entsprechend häufiger aufgeräumt wird<br />
als die der langlebigen Objekte. Dadurch wird erreicht, dass man eine kürzere Zeit für<br />
einen GC-Zyklus benötigt, da die einzelnen Partitionen weniger Objekte als das gesamte<br />
Programm enthalten. Zugleich wird die Wahrscheinlichkeit, ausreichend viele Objekte<br />
freizugeben, um ausreichend freien Speicher zur Verfügung zu haben, nicht zu sehr<br />
verringert, weil man in den häufig traversierten kurzlebigen Objekten auch die meisten<br />
wieder freizugebenden Kandidaten finden wird.<br />
Die Generational <strong>Collection</strong> erfordert in zwei Punkten einen Mehraufwand des GC-<br />
Algorithmus. Kanten, die im ursprünglichen Objektgraphen zwischen Objekten zweier
24 KAPITEL 3. GARBAGE-COLLECTION IN SOFTWARE<br />
verschiedener Partitionen liegen, müssen während der Traversierung zusätzlich überwacht<br />
werden, um zu verhindern, dass ein Objekt, welches nur aus einer anderen Partition<br />
heraus erreichbar ist, nicht versehentlich freigegeben wird.<br />
Ein zweites und grundlegenderes Problem ist die Vorhersage der Lebensdauer eines<br />
Objektes und damit seine Einordnung in die korrekte Partition von Objekten. In einem<br />
beliebigen Programm ist die Lebensdauer nicht vorhersehbar, weshalb hier Heuristiken<br />
zum Tragen kommen müssen.<br />
Der einfachste Ansatz, die Lebensdauer vorherzusagen, ist die so genannte weak generational<br />
hypothesis [18]. Sie besagt, dass: “Junge Objekte kürzer leben”. Das ermöglicht<br />
einfache Implementierungen, die auch in der Praxis zu guten Ergebnissen führen<br />
[16], [17]. Die Annahme beruht auf der intuitiven Beobachtung, dass in einem typischen<br />
Programm meist eine Gruppe von langlebigen Objekte existiert, die viele kurzlebige<br />
temporäre Objekte erstellen, die nach der Verwendung nicht mehr benötigt werden. Zur<br />
Implementierung werden neue Objekte immer in der kurzlebigen Klasse eingeordnet<br />
und zu den langlebigen Objekten verschoben, sobald sie einen GC-Zyklus überstanden<br />
haben.<br />
Komplexere Heuristiken verwenden noch weitere Informationen, wie zum Beispiel<br />
die Art der Referenz eines Objektes auf ein anderes [19]. Dabei wird ausgenutzt, dass<br />
Referenzen, die auf dem Stack des Programms liegen, meist zu kurzlebigeren Objekten<br />
führen als Referenzen im globalen Speicher.<br />
Charakteristika der Generational-<strong>Collection</strong><br />
• Bekannt als “Generation Scaveging”<br />
• Verbessert Echtzeit-Verhalten<br />
• Basiert auf der zukünftigen Lebensdauer der Objekte<br />
• Aufwändige Heuristiken
Kapitel 4<br />
<strong>Garbage</strong>-<strong>Collection</strong> in Hardware<br />
In diesem Kapitel wird ein <strong>Garbage</strong>-<strong>Collection</strong>-Algorithmus für Hardware (HW-GC)<br />
entworfen und entwickelt. Er eignet sich für eine hardwarebasierte Laufzeitumgebung,<br />
wie sie zum Beispiel in Embedded Systems vorkommen kann. Der Algorithmus basiert<br />
auf den Erkenntnissen, die in softwarebasierten Ansätzen entwickelt und in Kapitel 3<br />
vorgestellt worden sind. Er soll dabei besondere Rücksicht auf die Rahmenbedingungen<br />
der verwendeten Evaluationsplattform auf Basis eines FPGAs der Firma Xilinx nehmen.<br />
Diese Einschränkung durch äußere Rahmenbedingungen ist für die Entwicklung hardwarenaher<br />
Mechanismen typisch und stellt keinen Nachteil für die vorgestellten Methodiken<br />
dar, mit denen die bestehenden Algorithmen auf die Hardware adaptiert werden.<br />
In Kapitel 2 wurde bereits detailliert auf die Rahmenbedingungen der verwendeten<br />
Plattform eingegangen. Wesentlichen Einfluss auf die Entwicklung des Algorithmus haben<br />
die folgenden Punkte:<br />
• Die rekonfigurierbaren Module sind nebeneinander angeordnet.<br />
• Kommunikation zwischen den Modulen erfolgt ausschließlich über ein festgelegtes<br />
Interface, das möglichst kompakt gehalten wird.<br />
• Die Module verwenden ein gemeinsames Bussystem zum Datenaustausch. Das<br />
Kommunikationsprotokoll sieht vor, dass sämtliche Kommunikation über den zentralen<br />
Arbiter läuft.<br />
Diese Arbeit konzentriert sich auf die Mark-Sweep Ansätze (siehe Kapitel 3.4). Hauptgrund<br />
für diese Entscheidung ist die Tauglichkeit der Mark-Sweep <strong>Garbage</strong>-<strong>Collection</strong><br />
für eine Erweiterung zur Nebenläufigkeit. Selbst entworfene Hardware bietet ohnehin<br />
Nebenläufigkeit, so dass dieser Ansatz eine einfache und effiziente Implementierung<br />
eines nebenläufigen <strong>Garbage</strong>-<strong>Collection</strong>-Algorithmus verspricht. Des weiteren ist<br />
die Kommunikation zwischen einzelnen Objekten verhältnismäßig teuer. Ein Ansatz,<br />
der auf Reference-Counting (Abschnitt 3.3) basiert, erfordert eine Aktualisierung der
26 KAPITEL 4. GARBAGE-COLLECTION IN HARDWARE<br />
Referenz-Zähler bei jeder Zuweisung. Er wird durch die Kommunikation deutlich mehr<br />
beeinflusst als ein Mark-Sweep Ansatz, der nur zu wenigen Zeitpunkten aktiviert werden<br />
muss, um wieder Objekte freizugeben.<br />
4.1 Einordnung der <strong>Garbage</strong>-<strong>Collection</strong><br />
Eine weitere grundlegende Entscheidung ist die Einordnung der <strong>Garbage</strong> <strong>Collection</strong> in<br />
das Schichtenmodell aus Kapitel 2. Wie dort erläutert, lässt sich ein GC-Mechanismus<br />
entweder als Teil der Laufzeitumgebung in der Middleware, oder aber auf Anwendungsebene<br />
im Applikations-Layer implementieren.<br />
Wenn die <strong>Garbage</strong>-<strong>Collection</strong> auf Anwendungsebene realisiert wird, lässt sie sich innerhalb<br />
einer ComputingPage als normales Objekt realisieren. Als Mark-Sweep-Algorithmus<br />
benötigt die <strong>Garbage</strong>-<strong>Collection</strong> für jedes Objekt Zugriff auf deren Liste aller<br />
erreichbaren Objekte. Auf Anwendungsebene erfolgt das Auslesen dieser Liste über<br />
öffentlichen Methoden, die sämtliche Objekte implementieren müssen. Um den dabei<br />
entstehenden, erheblichen Kommunikationsaufwand zu vermeiden, ist die <strong>Garbage</strong>-<br />
<strong>Collection</strong> als Teil der Laufzeitumgebung realisiert. Sie verfügt über zusätzliche, eigene<br />
Kommunikationsmöglichkeiten (Drähte).<br />
Die Mechanismen zur Speicherverwaltung werden als Teil des Arbiters implementiert,<br />
der wiederum als Teil der Middleware parallel zu den frei konfigurierbaren ComputingPages<br />
existiert. Diese Lösung erlaubt es, spezielle Kommunikations- und Traversierungsstrukturen<br />
zu implementieren, welche die <strong>Garbage</strong>-<strong>Collection</strong> wesentlich beschleunigen.<br />
Eine zweite Entscheidung betrifft Systeme, die aus mehreren FPGAs bestehen und<br />
damit auch über mehrere Arbiter verfügen. Hier gibt es ebenfalls zwei Möglichkeiten:<br />
• Separate <strong>Garbage</strong>-<strong>Collection</strong> pro Chip: Sind die Kommunikationskanäle für die<br />
GC völlig transparent, dann reicht ein GC-Prozess aus. Es müssen Mechanismen<br />
entwickelt werden, ineffiziente Graphen-Traversierungen zu vermeiden. Diese<br />
treten auf, wenn häufiger als nötig Kanten über die Chip-Grenzen hinweg traversiert<br />
werden. Weil diese Kommunikationsart teurer als Traversierungen innerhalb<br />
der Objekte eines Chips ist, sollte sie vermieden werden.<br />
• Alternativ gäbe es die Möglichkeit, eine gemeinsame <strong>Garbage</strong>-<strong>Collection</strong> pro<br />
Chip zu realisieren, die mit Hilfe zusätzlicher Logik entsprechend synchronisiert<br />
werden muss.<br />
Mechanismen zur Unterstützung mehrerer Chips werden in dieser Arbeit nicht weiter<br />
betrachtet.
4.2. TRAVERSIERUNG 27<br />
4.2 Traversierung<br />
Kernstück und Hauptunterschied zur <strong>Garbage</strong>-<strong>Collection</strong> in Software ist die Traversierung<br />
im Mark-Prozess. Im Gegensatz zu einer klassischen Software Umgebung existieren<br />
in dem FPGA-basierten System weder ein gemeinsamer Speicher noch eine zentrale<br />
Memory-Mangement-Unit (MMU). Dies erfordert die Entwicklung völlig neuer Mechanismen,<br />
um die Referenzen, die in den ComputingPages abgelegt sind, auszulesen<br />
und zur nächsten ComputingPage weiterzuverfolgen.<br />
Ausgangspunkt der <strong>Garbage</strong>-<strong>Collection</strong> ist eine zentrale Komponente, die sich in der<br />
hierarchischen Struktur aus Kapitel 2 ähnlich wie der Arbiter einordnen lässt. Aufgabe<br />
dieser Komponente ist es, eine Liste der Wurzelknoten zu halten. Die Wurzelknoten<br />
entstehen, wenn über das Host-Interface ein neuer Prozess gestartet wird. An Ende der<br />
Verarbeitung wird das Ergebnis des Prozesses an das Host-System gesendet und der<br />
Wurzelknoten wieder gelöscht. Darüber hinaus muss die zentrale GC-Komponente entscheiden,<br />
wann ein neuer GC-Zyklus erforderlich ist, weil nicht mehr ausreichend viele<br />
freie ComputingPages zur Verfügung stehen. Um einen neuen GC-Zyklus zu starten,<br />
wird der normale Programmablauf gestoppt, der Mark- und der Sweep-Prozess werden<br />
durchgeführt und die nicht mehr erreichbaren Objekte werden der Liste der verfügbaren<br />
ComputingPages zugeteilt.<br />
Ein erster trivialer Ansatz zur Implementierung der Mark-Sweep-<strong>Collection</strong> wäre es,<br />
ausgehend von der zentralen Komponente sämtliche Referenzen des ersten Wurzelknotens<br />
auszulesen, lokal abzuspeichern und mit diesen Referenzen den Objektgraph weiter<br />
zu traversieren. Je nach verwendeter Speicherstruktur für die ausgelesenen Referenzen<br />
(Queue oder Stack) entsteht dadurch eine Breiten- oder Tiefensuche [20], die alle von<br />
der Wurzel aus erreichbaren Knoten besucht. In Abbildung 4.1 (linke Seite) wird solch<br />
eine Traversierung für einen einfachen Objektgraphen gezeigt. Für die Funktionalität<br />
der <strong>Garbage</strong>-<strong>Collection</strong> ist es zunächst unerheblich, ob eine Breiten- oder Tiefensuche<br />
implementiert wird.<br />
Der grundlegende Nachteil des beschriebenen Ansatzes ist die aufwändige Kommunikation.<br />
Es ist erforderlich, jede Referenz, die in einer aktiven ComputingPage vorkommt,<br />
zur zentralen <strong>Garbage</strong>-<strong>Collection</strong> -Komponente zu schicken. Sollte die gerade<br />
gelesene Referenz noch nicht als aktiv markiert sein, so muss in einem weiteren Kommunikationsschritt<br />
das referenzierte Objekt als aktiv markiert, und seine Referenzliste<br />
ausgelesen werden. Bei durchschnittlich n Referenzen pro ComputingPage und m aktiven<br />
ComputingPages auf dem FPGA ist es somit erforderlich, n ∗ m Referenzen zur<br />
zentralen Komponente hin und m Adressen zum Auslesen der nächsten zu traversierenden<br />
Komponente vom Arbiter zu den jeweiligen Objekten zurück zu schicken. Es sind<br />
also insgesamt n ∗ m + m Kommunikationsoperationen nötig.<br />
Unter Berücksichtigung der Randbedingungen, die eine Kommunikation mit dem Arbiter<br />
zu einer teuren Operation machen, ist ein Traversierungsschema wünschenswert,<br />
das mit wesentlich weniger Traversierungen auskommt. Eine effizientere Traversierung
28 KAPITEL 4. GARBAGE-COLLECTION IN HARDWARE<br />
wird durch verschiedene Ansätze ermöglicht. Durch eine nebenläufige Traversierung<br />
lassen sich mehrere Traversierungsschritte auf einmal durchführen. Im Idealfall lassen<br />
sich sämtliche Referenzen einer ComputingPage in einem einzigen Taktzyklus abarbeiten.<br />
Eine mögliche Traversierung hierfür ist in Abbildung 4.1 (rechte Seite) zu sehen.<br />
Anstelle von sieben Schritten bei der klassischen Tiefensuche (linke Seite) werden hier<br />
nur noch vier Schritte benötigt. Um diese Art der Traversierung zu gewährleisten, ist<br />
ein System notwendig, das es gestattet mit mehreren Modulen parallel zu kommunizieren.<br />
So ist in Schritt drei die zeitgleiche Aktivierung dreier verschiedener Knoten<br />
notwendig. Wegen den beschränkten Kommunikationsressourcen ist diese Forderung<br />
nicht erfüllbar.<br />
1<br />
1<br />
2<br />
6<br />
2<br />
2<br />
3<br />
5<br />
7<br />
3<br />
3 3<br />
4<br />
4<br />
a)<br />
b)<br />
Abbildung 4.1: Traversierungsreihenfolge eines Objektgraphen mit sequenzieller Traversierung<br />
mit Tiefensuche (a) und nebenläufiger Traversierung (b)<br />
In dieser Arbeit ist daher eine Struktur entwickelt worden, die durch Lokalisierung der<br />
Kommunikation eine mehrfache Nutzung des Bussystems innerhalb eines Zeitschritts<br />
erlaubt. Zudem wurde der Algorithmus dezentralisiert. Die Aufgabe der zentralen GC-<br />
Komponente ist es nur noch, die Traversierung in den Wurzelknoten anzustoßen, die<br />
ihrerseits die Traversierung in ihren erreichbaren Knoten selber anstoßen. Ein möglicher<br />
Flaschenhals der Kommunikation im Arbiter ist damit beseitigt.<br />
Die Idee dieses lokalisierten Ansatzes ist es, den Bus innerhalb jeder ComputingPage<br />
mit Hilfe von Registern in beide Richtungen zu isolieren. Anstelle eines einzigen<br />
Bussystems über alle Module hinweg, existieren damit mehrere kleinere Systeme. Jedes<br />
dieser kleineren Systeme kann somit zeitgleich genutzt werden. Eine bessere Auslastung<br />
der Systemressourcen ist nun möglich. Abbildung 4.2 verdeutlicht den Aufbau<br />
der Kommunikationsstrukturen zwischen beliebigen ComputingPages.<br />
Konkret enthält die Implementierung in jeder ComputingPage eine Liste aller Referenzen,<br />
die sie erreichen kann. Der GC besitzt nur die Liste der Wurzel-Knoten. Wenn ein<br />
GC-Zyklus startet, sendet er ein Signal zur Aktivierung an alle Wurzel-Knoten. Diese<br />
werden dadurch aktiviert, sie setzen ein Flag, das sie als besucht markiert und aktivieren<br />
ihrerseits alle ComputingPages aus der lokalen Referenzliste. Zur besseren Ausnutzung
4.2. TRAVERSIERUNG 29<br />
Computing Page (Adresse 3)<br />
Computing Page (Adresse 2)<br />
Computing Page (Adresse 1)<br />
LeftReferences RightReferences LeftReferences RightReferences<br />
LeftReferences<br />
RightReferences<br />
LeftOut<br />
RightIn<br />
LeftOut<br />
RightIn<br />
LeftOut<br />
RightIn<br />
LeftIn<br />
SweepOut<br />
Register<br />
RightOut LeftIn<br />
Register<br />
RightOut<br />
LeftIn<br />
Flag Flag Flag<br />
RightOut<br />
SweepIn SweepOut<br />
SweepIn SweepOut<br />
SweepIn<br />
Register<br />
Abbildung 4.2: Lokalisierter Bus zur <strong>Garbage</strong>-<strong>Collection</strong><br />
der Systemressourcen ist die Referenzliste aufgeteilt in Referenzen, die links, und solche,<br />
die rechts vom aktuellen Knoten liegen. Referenzen, die auf Objekte links von der<br />
aktuellen Position verweisen, brauchen nur über die lokalisierten Datenleitungen zu den<br />
linken Nachbarn gesendet werden. Der Bus in die rechte Richtung ist damit für andere<br />
Signale frei. Trotz einer besseren Ausnutzung der Ressourcen kann es zu Konflikten<br />
kommen, wenn ein Modul gleichzeitig ein von links kommendes Aktivierungssignal<br />
weiterleiten und selber ein Aktivierungssignal nach rechts senden will. Um hierbei Kollisionen<br />
zu vermeiden, dürfen Objekte den Bus nur dann selber verwenden, wenn sie<br />
nicht gerade ein anderes Signal weitersenden.<br />
Abbildung 4.3 zeigt beispielhaft eine Traversierung in der implementierten Datenstruktur.<br />
Jede der fünf nebeneinander angeordneten ComputingPages enthält ein Register<br />
mit Referenzen, die nach links und ein zweites Register für Referenzen, die nach<br />
rechts verweisen. Die Zahlen auf den Kanten entsprechend den Signalen, die zu einem<br />
festgelegten Zeitschritt auf den lokalisierten Bussystemen gesendet werden, die Zeilen<br />
entsprechen den einzelnen Zeitschritten der Traversierung. Zum Zeitschritt t = 4 tritt<br />
in der ComputingPage 3 einer der beschriebenen Buskonflikte auf. Das Modul muss<br />
das von rechts kommende Signal weiterleiten, und kann die eigene Referenz erst im<br />
nächsten Schritt versenden.<br />
Nach abgeschlossener Traversierung ist es im Sweep-Schritt erforderlich, der zentralen<br />
<strong>Garbage</strong>-<strong>Collection</strong>-Komponente im Arbiter mitzuteilen, welche ComputingPages bei<br />
der Traversierung aktiviert worden sind. Hierzu dient ein ein Bit breites Schieberegister,<br />
das als Sweep-Leitung bezeichnet wird. Bei der Traversierung wurde in jedem aktivierten<br />
Modul ein entsprechendes Flag gesetzt. Diese Flags sind in dem Schieberegister<br />
hintereinandergeschaltet und werden schrittweise zum Arbiter verschoben. Dieser setzt<br />
daraufhin für jedes nicht gesetzte Flag die korrespondierende ComputingPage auf die<br />
Liste der verfügbaren freien Logikressourcen.<br />
Ein weiteres Problem ist, dass bei der dezentralen Traversierung nicht fest steht, wann
30 KAPITEL 4. GARBAGE-COLLECTION IN HARDWARE<br />
Zeit<br />
ID 5 ID 4<br />
ID 3<br />
ID 2<br />
ID 1<br />
t=1<br />
1<br />
4<br />
5<br />
1<br />
3<br />
4<br />
5<br />
2<br />
FromArbiter<br />
ID 5<br />
ID 4<br />
ID 3<br />
ID 2<br />
ID 1<br />
t=2<br />
1<br />
4<br />
5<br />
1<br />
3<br />
4<br />
2<br />
5<br />
FromArbiter<br />
ID 5<br />
ID 4<br />
ID 3<br />
ID 2<br />
ID 1<br />
t=3<br />
1<br />
4<br />
5<br />
1<br />
3<br />
4<br />
FromArbiter<br />
activated<br />
ID 5<br />
ID 4<br />
ID 3 ID 2<br />
ID 1<br />
t=4<br />
1<br />
4<br />
5<br />
1<br />
4<br />
FromArbiter<br />
activated<br />
activated<br />
ID 5<br />
ID 4<br />
ID 3 ID 2<br />
ID 1<br />
t=5<br />
1<br />
4<br />
5<br />
1<br />
FromArbiter<br />
activated<br />
activated<br />
activated<br />
ID 5<br />
ID 4<br />
ID 3 ID 2<br />
ID 1<br />
5<br />
t=6<br />
1<br />
FromArbiter<br />
activated<br />
activated<br />
activated<br />
activated<br />
ID 5<br />
ID 4<br />
ID 3 ID 2<br />
ID 1<br />
5<br />
t=7<br />
1<br />
FromArbiter<br />
activated<br />
activated<br />
activated<br />
activated<br />
ID 5<br />
ID 4<br />
ID 3 ID 2<br />
ID 1<br />
t=8<br />
FromArbiter<br />
activated<br />
activated<br />
activated<br />
activated<br />
activated<br />
Abbildung 4.3: Ablauf einer Traversierung im HW-GC-Algortihmus
4.3. FRAGMENTIERUNG 31<br />
sämtliche, lokal ablaufenden Aktivierungen zu Ende sind. Diese Problem wird mit Hilfe<br />
eines "Heartbeat”-Signals gelöst, das zum ressourcensparen ebenfalls auf der Sweep-<br />
Leitung realisiert ist. Eine ComputingPage, die aktiviert wurde und noch nicht zu allen<br />
Einträge aus ihrer Referenzliste ein Aktivierungssignal gesendet hat, schickt auf der<br />
Sweep-Leitung eine ’1’ in Richtung des Arbiters. Weil die Sweepleitung als Schieberegister<br />
realisiert ist, die in jeder ComputingPage ein Register besitzt, erhält man somit<br />
bei n Objekten nach maximal 2 ∗ n Takten eine ’1’ in der zentralen GC-Einheit. Dieser<br />
maximale Fall tritt dann ein, wenn das entfernteste Objekt aktiviert wird. Das Aktivierungssignal<br />
für das Objekt benötigt n Takte um die Register der dazwischenliegenden<br />
Module zu durchlaufen. Noch einmal n Takte sind notwendig, damit das “Heartbeat”-<br />
Signal durch das Schieberegister wieder zurück gelangt. Trifft also innerhalb von 2 ∗ n<br />
Takten keine “Heartbeat”-Signal in der zentralen GC-Komponente ein, so ist die Traversierung<br />
des Mark-Prozess abgeschlossen. Mit Hilfe eines globalen Enable-Signals<br />
wird daraufhin der Sweep-Modus aktiviert, um die Liste der erreichbaren Objekte zu<br />
generieren.<br />
Mögliche Lösungen, die in der Lage wären, in weniger als 2 ∗ n Takten das Ende der<br />
Traversierung zu bestimmen, benötigen weitere Routingressourcen. Aus diesem Grund<br />
wurde auf sie verzichtet.<br />
4.3 Fragmentierung<br />
In einem softwarebasierten, objektorientierten System gibt es zwei Arten, auf die sich<br />
eine Fragmentierung des Speichers auswirken kann (siehe Abschnitt 3.5.1). Wird der<br />
Speicher in zu viele kleine freie Bereiche fragmentiert, so besteht die Möglichkeit, das<br />
eine Anforderung für eine zusammenhängende Menge freien Speicher, wie sie etwa<br />
beim Erstellen eines Objekts vorkommt, von der Speicherverwaltung nicht erfüllt werden<br />
kann. In diesem Fall ist zuerst eine zeitaufwändige Defragmentierung des Speichers<br />
notwendig. Die meisten <strong>Garbage</strong>-<strong>Collection</strong>-Algorithmen für Softwaresysteme beinhalten<br />
Mechanismen, um die Fragmentierung des Speichers gering zu halten.<br />
Für den vorgestellten hardwarebasierten Entwicklungsansatz existiert dieser Fall von<br />
Fragmentierung nicht. Ihre Ursache liegt in der Anforderung von Speicherbereichen<br />
verschiedener Größe. Da sämtliche ComputingPages eine konstante Größe besitzen und<br />
entweder vollständig belegt oder vollständig verfügbar sind, kann keine Fragmentierung<br />
auftreten.<br />
Eine zweite Art der Fragmentierung tritt bei einer Softwareimplementierung durch<br />
den Verlust von Lokalität auf. Die Caching-Mechanismen eines modernen Prozessors<br />
erlauben es, auf nah beieinander liegende Speicherbereiche schneller zu zugreifen, als<br />
auf beliebige, weiter entfernt liegende Speicherbereiche. Ist ein Speicher stark fragmentiert,<br />
so liegen auch die Speicherbereiche der Objekte nicht mehr nah beieinander.<br />
Überraschenderweise existiert in der Hardwareimplementierung ein ähnliches Phäno-
32 KAPITEL 4. GARBAGE-COLLECTION IN HARDWARE<br />
men. Durch die Registerstruktur auf den Signalleitungen benötigt ein Signal länger, um<br />
weiter entfernt liegenden ComputingPages zu erreichen. So ist nur ein Taktzyklus erforderlich,<br />
um ein benachbartes Objekt zu aktivieren. Liegt das zu aktivierende Objekt<br />
hingegen am anderen Rand des FPGAs, so braucht das Signal mehrere Takte, um sämtliche<br />
Register zu durchlaufen. Es existiert also eine bestimmte Zuordnung der Objekte<br />
eines Programms zu den ComputingPages, die eine minimale Traversierungsdauer gewährleistet.<br />
Nutzt man die Möglichkeiten eines FPGAs, ein Modul samt seiner aktuellen<br />
Registerbelegung auszulesen und das Modul an anderer Position wieder auf den Chip<br />
zu laden, so ist man in der Lage die Lokalitätsstruktur zu modifizieren. Bisher existiert<br />
weder ein Algorithmus noch eine Heuristik, um zu bestimmen, wie eine geometrische<br />
Anordnung der ComputingPages aussehen muss, damit die Dauer der Traversierung<br />
minimiert wird. Darüber hinaus benötigt das Verschieben von rekonfigurierbaren Modulen<br />
zu viel Zeit, um praktikabel zu sein.<br />
4.4 Nebenläufigkeit<br />
Zur Implementierung einer nebenläufigen <strong>Garbage</strong>-<strong>Collection</strong> gibt es verschiedene Möglichkeiten.<br />
Kernelement aller solcher Algorithmen sind Barrieren, die in Abschnitt 3.5.2<br />
bereits vorgestellt wurden. Zweck dieser Barrieren ist es, gewisse Operationen einzuschränken<br />
um invariante Bedingungen aufzustellen. Diese Invarianzen erlauben eine<br />
nebenläufige <strong>Garbage</strong>-<strong>Collection</strong>.<br />
Herkömmliche Ansätze aus Softwareimplementierung bilden zumeist entweder eine<br />
read-barrier, die das Lesen von Daten einschränkt, oder aber eine write-barrier, um die<br />
Aktualisierung von Referenzen währenden einem GC-Lauf einzuschränken.<br />
Zum Entwurf eines sinnvollen Algorithmus für dynamisch rekonfigurierbare Systeme<br />
ist es sinnvoll, eine Liste möglicher Operationen aufzustellen, durch die sich der Objektgraph<br />
ändern kann. Ein Objekt A kann drei verschiedene Arten von Operationen<br />
durchführen, die Einfluss auf den Objektgraph haben:<br />
1. Der Aufruf eines Konstruktors lässt den Arbiter ein weiteres Objekt B instanziieren.<br />
Die Adresse dieses neuen Objekts wird dem aufrufenden Objekt zurückgeliefert.<br />
Der Objektgraph wird um eine gerichtete Kante vom Knoten A zum Knoten<br />
(B) erweitert.<br />
2. Beim Aufrufen einer Methode eines anderen Objekts kann dieses als Rückgabewert<br />
eine Referenz auf ein Objekt C liefern. Auch hier wird der Objektgraph um<br />
eine gerichtete Kante vom Knoten A zum Knoten C erweitert.<br />
3. Durch Operationen innerhalb eines Objektes können Referenzen gelöscht werden<br />
(s = NULL). Hierbei wird die Kante vom Objekt A zum Objekt s entfernt.
4.4. NEBENLÄUFIGKEIT 33<br />
Neue Kanten können durch diesen Fall nicht entstehen, da für das Objekt A keine<br />
Möglichkeit besteht, an weitere neue Referenzen zu kommen.<br />
Das Hinzufügen von Kanten, während in einem nebenläufigen Prozess die <strong>Garbage</strong>-<br />
<strong>Collection</strong> aktiv ist, erzeugt eine andere Art von Fehlern, als das Entfernen von Kanten.<br />
Durch das Hinzufügen von Kanten kann es zu Situationen kommen, wie sie in<br />
Abschnitt 3.5.2 in der Abbildung 3.1 dargestellt sind. Diese Fehler sind unbedingt zu<br />
vermeiden.<br />
Für die Entfernung von Kanten kann man den Objektgraphen in zwei Mengen unterteilen;<br />
Knoten die bereits traversiert worden sind und Knoten die noch traversiert werden<br />
müssen. Eine getrennte Betrachtung für den Knoten, der aktuell traversiert wird, ist hier<br />
nicht notwendig. In einem taktsynchronen Design lassen sich der Besuch eines Knotens<br />
und das Löschen einer Referenz (einer Kante im Objektgraph) als atomare Operation<br />
betrachten. Wird eine Kante aus der zweiten Menge gelöscht, so stellt dies kein Problem<br />
da. Die Traversierung wird korrekt erkennen, dass die Kante nicht mehr existiert,<br />
sobald die entsprechenden Postion im Graphen erreicht worden ist. Sämtliche Knoten<br />
aus dem abgearbeiteten Bereich sind bereits als aktiv markiert. Wird hier eine Kante<br />
gelöscht, so besteht die Möglichkeit, dass dadurch ein Objekt unerreichbar wird. Auf<br />
die Funktionalität des Programms hat dies Fall keinen Einfluss, weil das Objekt ja nicht<br />
mehr verwendet wird. Im nächsten GC-Zyklus wird das Objekt dann korrekt als nicht<br />
mehr erreichbar erkannt und verarbeitet. Damit ist es nicht notwendig, lokale Operationen<br />
in einem Objekt, die höchstens Referenzen löschen können, einzuschränken.<br />
Weil nicht mehr alle Objekte innerhalb eines GC-Zyklus zuverlässig als unerreichbar<br />
erkannt werden können, wird die Vollständigkeit aus Definition 3.0.2 zu einer partiellen<br />
Vollständigkeit abgeschwächt.<br />
Definition 4.4.1 (Paritelle Vollständigkeit)<br />
Alle nicht mehr erreichbaren Objekte werden innerhalb von höchstens zwei aufeinander<br />
folgenden GC-Zyklen erkannt und freigegeben.<br />
Die beiden Operationen zum Hinzufügen von Referenzen verwenden zur Kommunikation<br />
das Bus-System und damit den Arbiter. Gibt der Arbiter während eines aktiven<br />
GC-Zyklus also keinem Objekt die Berechtigung den Bus zu verwenden, so kann auch<br />
kein Objekt neue Referenzen erhalten. Diese Deaktivierung des Bus-Systems bildet eine<br />
kombinierte Lese- und Schreibbarriere. Allerdings ist diese Barriere auf globale Kommunikation<br />
(also Kommunikation zwischen verschiedenen Objekten) beschränkt. Der<br />
Ablauf innerhalb einer ComputingPage bleibt davon unbeeinflusst.<br />
Der Sweep-Prozess basiert nur auf den Flags, die während der Traversierung des<br />
Mark-Prozesses gesetzt wurden. Eine Modifikation dieser Register durch normale Methoden<br />
der Objekte ist nicht nötig. Daher ist es möglich, die Barriere im Arbiter bereits<br />
nach dem Mark zu deaktivieren. Sweep-Prozess und normaler Programmablauf können<br />
dann ohne Einschränkungen parallel ablaufen.
34 KAPITEL 4. GARBAGE-COLLECTION IN HARDWARE<br />
4.5 Generational <strong>Collection</strong><br />
Die Generational <strong>Collection</strong> (siehe Abschnitt 3.5.3) ist eine Methode, um in einem GC-<br />
Zyklus nur eine Partition der aktiven Objekte auf nicht mehr erreichbare Objekte zu<br />
untersuchen. Diese Teilmengen sind so gewählt, dass sie mit höherer Wahrscheinlichkeit<br />
wieder freizugebende Objekte enthalten. Aufgrund der geringen Anzahl an maximal<br />
realisierbaren Objekten auf der aktuellen Plattform und dem hohen Verwaltungsaufwand,<br />
den die Generational <strong>Collection</strong> mit sich bringt, ist dieses Konzept nicht Bestandteil<br />
einer <strong>Garbage</strong>-<strong>Collection</strong> in Hardware.<br />
Ein möglicher Einsatz für die Zukunft wäre der Einsatz von Methoden der Generational<br />
<strong>Collection</strong> für die Auswahl eines Wurzelknotens zum Starten der <strong>Garbage</strong>-<br />
<strong>Collection</strong>. Falls mehrere Threads (und damit mehrere Wurzelknoten) auf dem FPGA<br />
zeitgleich ablaufen, wäre es wünschenswert, die <strong>Garbage</strong>-<strong>Collection</strong> auf dem Thread<br />
zu starten, der mit größerer Wahrscheinlichkeit nicht mehr erreichbare Objekte enthält.<br />
Hierzu könnten die entsprechenden Heuristiken verwendet werden. Es bleibt trotzdem<br />
ein nicht unerheblicher Mehraufwand in der Verwaltung der Objekte, um zwischen tatsächlich<br />
nicht mehr erreichbaren Objekten und Objekten der anderen Threads zu unterscheiden.<br />
Aus diesem Grund wurde auf eine entsprechenden Berücksichtigung bei der<br />
Implementierung verzichtet.<br />
4.6 Der Algorithmus der Hardware-<strong>Garbage</strong>-<strong>Collection</strong><br />
(HW-GC)<br />
Im Folgenden werden die Ansätze, die in den letzten Abschnitten entwickelt worden<br />
sind, zu einem konkreten Algorithmus zusammengefasst. Dies sind insbesondere das lokalisierte<br />
Traversierungsschema, sowie die nebenläufige Implementierung von <strong>Garbage</strong>-<br />
<strong>Collection</strong> und normalen Programmablauf.<br />
Dieser HW-GC Algorithmus ist speziell auf die Datenstruktur der verwendeten FPGA-<br />
Plattform ausgelegt. Die für die <strong>Garbage</strong>-<strong>Collection</strong> relevanten Punkte lauten in kurzen<br />
Stichworten zusammengefasst:<br />
• Jedes Objekt (jede ComputingPage) führt eine Liste mit den Referenzen aller Objekte,<br />
auf die sie zugreifen kann. Diese Liste wird ausschließlich vom Objekt<br />
selber modifiziert. Der <strong>Garbage</strong> Collector kann sie auslesen.<br />
• Neue Einträge können nur in zwei Fällen in die Referenzliste eingetragen werden:<br />
Erstens, wenn das Objekt ein neues Kind-Objekt erzeugt, oder zweitens, wenn<br />
eine neue Referenz als Ergebnis beim Methodenaufruf eines anderen Objektes<br />
zurückgeliefert wird. Beide Fälle erfordern eine Kommunikation über den Arbiter.
4.6. DER ALGORITHMUS DER HARDWARE-GARBAGE-COLLECTION<br />
(HW-GC) 35<br />
• Ohne Kommunikation mit dem Arbiter kann ein Objekt nur interne Funktionen<br />
abarbeiten. Dabei können Referenzen aus der Referenzliste gelöscht werden (z.B.<br />
s = NULL) aber keine neuen hinzukommen. Für neue Referenzen wäre in jedem<br />
Fall eine Kommunikation (Methodenaufruf oder Instantiierung) über den Arbiter<br />
nötig.<br />
• Es gibt innerhalb eines Prozesses immer nur ein aktives Objekt. Diese Bedingung<br />
ist für die Funktionalität unerheblich, hilft aber bei der späteren Analyse der Leistungsfähigkeit<br />
erheblich weiter. Im Sinne der <strong>Garbage</strong>-<strong>Collection</strong> ist die Anzahl<br />
der aktiven Knoten unerheblich. Wesentlich ist nur, dass zur Kommunikation untereinander<br />
ausschließlich der vom Arbiter verwaltete Bus zum Einsatz kommt.<br />
• Wenn man die Kommunikation über den Arbiter deaktiviert, können sich die Referenzlisten<br />
nur noch verkleinern. In dem aufgespannten Objektgraph können also<br />
keine neuen Kanten eingefügt werden. Bei der Traversierung dieses Baumes<br />
mittels eines Mark-Sweep-Algorithmus ist garantiert, dass sämtliche nicht markierten<br />
Objekte gelöscht werden dürfen.<br />
Damit ergibt sich der konkrete Algorithmus HW-GC. Zunächst wird der Algorithmus<br />
informell beschrieben:<br />
Algorithmus 1 Algorithmus HW-GC in einfachem Pseudocode<br />
1: Loop<br />
2: Deaktiviere vor der Mark-Phase die Kommunikation über den Arbiter<br />
3: Aktiviere alle ComputingPages, die in der Liste der Wurzelknoten referenziert<br />
sind<br />
4: Wiederhole<br />
5: Wenn (ein Objekt wurde aktiviert) dann<br />
6: Setze ein internes Flag<br />
7: Sende ein Heartbeat-Signal zum Arbiter<br />
8: Aktiviere alle Objekte, die in der Referenzliste enthalten sind<br />
9: Ende<br />
10: Bis die Traversierung abgeschlossen ist<br />
11: Die Kommunikation über den Arbiter wird wieder freigeschaltet<br />
12: Lese das Sweep-Signal aus und erstelle daraus die Liste der nicht besuchten Module.<br />
Diese können rekonfiguriert werden.<br />
13: Wiederhole<br />
14: Normaler Programmablauf. Es findet keine Speicherbereinigung statt.<br />
15: Bis ein neuer GC-Zyklus erforderlich ist<br />
16: Loop Ende<br />
Dieser Ansatz kommt auch mit mehreren lokalen Threads zurecht. Diese werden durch
36 KAPITEL 4. GARBAGE-COLLECTION IN HARDWARE<br />
mehrere Einträge in der Liste der Wurzelknoten realisiert. Eine Erweiterung auf mehrere<br />
zusammengeschaltete FPGAs ist ebenfalls möglich. Durch das abstrakte Adressierungsschema<br />
ist es möglich, die Kommunikation über die Grenzen eines Bausteins hinweg<br />
für den <strong>Garbage</strong> Collector transparent zu gestalten. In diesem Fall ist das Problem der<br />
Fragementierung zu berücksichtigen. Da die Kommunikation zwischen den verschiedenen<br />
Bausteinen voraussichtlich teurer ist als die lokale Kommunikation, sollten bei der<br />
<strong>Garbage</strong>-<strong>Collection</strong> so selten wie möglich Signale zur Aktivierung eines Moduls über<br />
Chip-Grenzen hinweg laufen. Wenn ein Modul über zwei verschiedene Kantenpfade erreichbar<br />
ist, dann sollte der Pfad bevorzugt werden, der lokal verläuft, selbst wenn er<br />
aus mehr Kanten besteht.<br />
Die Beschreibung des Algorithmus in Pseudocode ist zu Gunsten der Verständlichkeit<br />
semantisch uneindeutig. Das gleichzeitige Ablaufen von mehreren nebenläufigen Prozessen<br />
ist mit den üblichen Mitteln zur Beschreibung solcher Algorithmen nur schwer<br />
zu modellieren. Eine exaktere Darstellung des HW-GC-Algorithmus ist in Algorithmus<br />
4.4 dargestellt. Zur Beschreibung wird die Hardwarebeschreibungssprache VHDL<br />
verwendet. Einige komplexe Operationen sind zu informellen Beschreibungen vereinfacht<br />
worden. Der Code besteht aus den zwei wesentlichen Komponenten zur <strong>Garbage</strong>-<br />
<strong>Collection</strong>. In der linken Spalte ist die Funktionalität des Arbiters angegeben, die rechte<br />
Spalte enthält die Funktionalität einer einzelnen ComputingPage. Der vorgestellte Algorithmus<br />
kommt mit einem Minimum an Datenleitungen aus.<br />
Die Schnittstelle zwischen zwei ComputingPages besteht jeweils aus zwei unidirektionalen<br />
Leitungen, um Aktivierungssignale in Form von Adressen zu übertragen. In der<br />
aktuellen Implementierung sind die Adressen mit vier Bit Breite implementiert. Es wird<br />
also ein acht Bit breiter Bus zwischen zwei Modulen benötigt. Darüberhinaus benötigt<br />
der Algorithmus ein globales Enable-Signal zum Aktivieren der <strong>Garbage</strong>-<strong>Collection</strong>.<br />
Das Sweep-Signal muss seine Daten lediglich ein Richtung des Arbiters weiterleiten.<br />
Es kann also ebenfalls mit einem Bit realisiert werden.<br />
Eine weitere Optimierung der benötigten Systemressourcen ist möglich, weil während<br />
des Mark-Prozesses das normale Bus-System nicht verwendet wird. In dieser Zeit können<br />
die bereits vorhanden Datenstrukturen benutzt werden, um die Module zu aktivieren.<br />
Damit benötigt die <strong>Garbage</strong>-<strong>Collection</strong> gegenüber der bisherigen objektorientierten<br />
Hardwareumgebung lediglich zwei weitere Leitungen:<br />
1. Das Enable-Signal der <strong>Garbage</strong>-<strong>Collection</strong><br />
2. Das Sweep-Signal.
4.6. DER ALGORITHMUS DER HARDWARE-GARBAGE-COLLECTION<br />
(HW-GC) 37<br />
e n t i t y A r b i t e r i s<br />
port (<br />
SweepIn : in s t d _ l o g i c ;<br />
EnableGC : out s t d _ l o g i c ;<br />
L e f t O u t : out t A d d r e s s ;<br />
) ;<br />
case GCState i s<br />
−− normal program i s r u n n i n g<br />
−− w a i t u n t i l n e x t GC−Cycle<br />
when I d l e =><br />
i f ( DelayCounter = MaxTime ) then<br />
GCState <br />
EnableGC
38 KAPITEL 4. GARBAGE-COLLECTION IN HARDWARE
Kapitel 5<br />
Analyse des HW-GC-Algorithmus<br />
In diesem Kapitel sollen die Anforderungen und Möglichkeiten des Algorithmus zur<br />
hardwarebasierten <strong>Garbage</strong>-<strong>Collection</strong> (HW-GC) analysiert werden. Insbesondere soll<br />
ein Versuch unternommen werden, die Leistungsfähigkeit des HW-GC-Algorithmus zu<br />
bewerten. Mit dem Mark-Sweep-Algorithmus zur Speicherverwaltung und der Betrachtung<br />
einer ComputingPage als Objekt existieren zwischen der Infrastruktur eines herkömmlichen<br />
Desktop-PCs und der verwendeten FPGA-Plattform einige Gemeinsamkeiten.<br />
Trotzdem erschweren substanzielle Unterschiede den Vergleich der <strong>Garbage</strong>-<br />
<strong>Collection</strong> zwischen den beiden Systemen. Insbesondere Verfahren zur Analyse einer<br />
softwarebasierten Speicherverwaltung lassen sich nicht ohne weiteres auf den implementierten<br />
HW-GC abbilden.<br />
Von besonderem Interesse sind vor allem drei Gesichtspunkte:<br />
• Das FPGA erlaubt nur eine eingeschränkte Anzahl von Objekten. Die Plattform<br />
ist nur unzureichend auf die Verwaltung und Instanziierung vieler ComputingPages<br />
zugeschnitten. Eine Analogie hierzu wäre eine Softwareumgebung mit stark<br />
eingeschränktem Speicherplatz. Für solche Plattformen sind GC-Mechanismen<br />
und die zugehörigen Analysen jedoch unüblich.<br />
• Die Hardwareplattform bietet Möglichkeiten zur Implementierung eines voll synchronen<br />
und nebenläufigen Designs. In einer Softwareumgebung sind diese Möglichkeiten<br />
nicht gegeben.<br />
• Die verwendete Datenstruktur zum Speichern der Objekte besteht aus mehreren<br />
ComputingPages die über einen Arbiter miteinander kommunizieren. Die Struktur<br />
unterscheidet sich damit deutlich vom linear angeordneten Speicher eines<br />
Heaps, wie er in einer softwarebasierten Laufzeitumgebung zum Einsatz kommt.
40 KAPITEL 5. ANALYSE DES HW-GC-ALGORITHMUS<br />
5.1 Objektstruktur<br />
Die Leistungsfähigkeit von GC-Algorithmen hängt stark von der Struktur des Programms<br />
ab, das zur Laufzeit im System ausgeführt wird. Diese Strukturen können je nach implementiertem<br />
Programm sehr unterschiedlich sein. Das hat auch Auswirkungen auf die<br />
Leistungsfähigkeit der jeweiligen <strong>Garbage</strong>-<strong>Collection</strong>. Blackburn [22] hat eine Vielzahl<br />
verschiedener GC-Mechanismen und Programme miteinander verglichen. Seine Untersuchung<br />
kommt zu dem Schluss, dass es keinen optimalen <strong>Garbage</strong> Collector gibt. Es<br />
lassen sich zwar Klassen von Programmen bestimmen, die besonders stark von einem<br />
bestimmten Algorithmus zur <strong>Garbage</strong>-<strong>Collection</strong> profitieren, jedoch existieren ebenso<br />
Programme, für welche die Annahmen und Heuristiken des GC-Algorithmus ungünstig<br />
sind. Dabei handelt es sich nicht um synthetische Programme, die gezielt auf die<br />
Schwachstellen der <strong>Garbage</strong>-<strong>Collection</strong> abzielen, sondern um typische, auch in der Praxis<br />
zum Einsatz kommende Programme.<br />
Weil ein Vergleich mit anderen GC-Algorithmen nicht immer praktikabel ist, gibt es<br />
trotz der beschriebenen Probleme einige Ansätze zur Analyse einer einzelnen <strong>Garbage</strong>-<br />
<strong>Collection</strong>. So ist es möglich, das System mit Hilfe einer repräsentativen Menge von<br />
Programmen zu testen [21], [22]. Dieser Ansatz ermöglicht eine realistische Aussage<br />
der Leistungsfähigkeit. Hauptproblem der Methodik ist es, eine passende Menge von<br />
Programmen auszuwählen und sicherzustellen, dass ein Großteil aller möglichen Programme<br />
eine große Ähnlichkeit mit einem der Testprogramme bietet. Für den hier vorgestellten<br />
Ansatz ist dieser Lösungsweg wenig erfolgversprechend. Es existieren noch<br />
nicht ausreichend viele verschiedene Programme, um repräsentative Aussagen treffen<br />
zu können. Zudem bietet die geringe Maximalanzahl von Objekten auf der Evaluierungsplattform<br />
nur wenig Spielraum beim Entwurf neuer Programme.<br />
Für diese Arbeit ist der Ansatz der synthetischen Modelle erfolgversprechender. Dabei<br />
wird versucht, ein synthetisches Modell zu entwickeln, das Aussagen bezüglich der<br />
Objektstruktur beliebiger Programme zulässt. Die synthetischen Modelle lassen sich danach<br />
unterteilen, ob sie aus Struktur- oder Verhaltensanalysen gewonnen wurden. Die<br />
Strukturanalysen versuchen aus der Struktur des Speichers Rückschlüsse auf die Lebensdauer<br />
der referenzierten Objekte zu ziehen [23], [24]. Ergebnis dieser Arbeiten<br />
ist beispielsweise, dass die Lebensdauer eines Objektes, das vom Heap aus referenziert<br />
wird, höher ist, als die Lebensdauer eines Objektes, das vom Stack aus referenziert wird.<br />
Im Gegensatz zu Strukturanalyse versuchen die Verhaltensanalysen, mathemathische<br />
Modelle für die Lebensdauer beliebiger Objekte zu finden. Eine Kenntnis der Struktur<br />
erlaubt zwar möglicherweise bessere Modelle, ist aber nicht prinzipiell erforderlich.<br />
Stefanovic hat verschiedene derartige Modelle miteinander verglichen [25]. Seine Arbeit<br />
kommt zu dem Schluss, dass diese Modelle derzeit noch nicht sinnvoll einsetzbar<br />
sind. Die geschätzte Lebensdauer eines Objekts weicht zur Zeit noch zu stark von der<br />
tatsächlichen Lebensdauer ab. Eine Ausnahme bildet die, bereits 1983 von Lieberman<br />
[18] aufgestellte, weak generational hypothesis. Sie besagt, dass jüngere Objekte auch
5.2. SOFTWAREBASIERTES SIMULATIONSMODELL 41<br />
eine kürzere zukünftige Lebenserwartung haben. Diese Hypothese hat sich in der Praxis<br />
vielfach bewährt und bildet die Basis der Generational <strong>Collection</strong>, die bereits in<br />
Kapitel 3.5.3 vorgestellt wurde.<br />
Alle vorgestellten Modelle der Objektstruktur basieren auf statistischen Annahmen.<br />
Sie sind so ausgerichtet, dass sie für Anwendungen in einer klassischen Laufzeitumgebung<br />
(Desktop-PC) die besten Ergebnisse liefern. Sie lassen sich nur schlecht auf beliebige<br />
Programme übertragen, deren Struktur sich stark von einer Softwareanwendung<br />
unterscheidet. Weil sich die hier verwendete Hardwareumgebung schon in der geringen<br />
Maximalanzahl von Objekten stark von klassischen Laufzeitumgebungen unterscheidet,<br />
sind auch deren Heuristiken nur unzureichend übertragbar.<br />
5.2 Softwarebasiertes Simulationsmodell<br />
Die erste Aufgabe einer Analyse muss die Überprüfung der Funktionalität des entwickelten<br />
Algorithmus sein. Eine erste Evaluation der Algorithmen fand in einem Software-<br />
Simulationsmodell auf Java-Basis [26], [27] statt. Ziel des Modells war es vor allem, zu<br />
überprüfen ob die Nebenläufigkeit der <strong>Garbage</strong>-<strong>Collection</strong> zu unzulässigen Objektstrukturen<br />
führen kann. Hierzu ist vor allem eine Simulation der Traversierung des Mark-<br />
Prozesses notwendig.<br />
Durch den Einsatz von mehreren Threads und einer Multi-Prozessor-Plattform lassen<br />
sich die nebenläufigen Aspekte der HW-GC evaluieren.<br />
Um in der Softwaresimulation verlässliche Aussagen treffen zu können, ist die Implementierung<br />
einer passenden Datenstruktur notwendig. Diese Datenstruktur muss das<br />
Verhalten der ComputingPages der Hardwareumgebung möglichst gut annähern.<br />
Im Java-basierten Simulationsmodell wird eine Struktur mit 16 ComputingPages erzeugt.<br />
Diese besitzen eine zufällig generierte Menge von Referenzen aufeinander. In<br />
zwei parallelen Threads wird eine nebenläufige <strong>Garbage</strong>-<strong>Collection</strong> simuliert. Ein Thread<br />
simuliert den Mutator, während der andere Thread die Aufgabe des Collectors übernimmt.<br />
Der HW-GC-Algorithmus aus Kapitel 4 deaktiviert während der aktiven <strong>Garbage</strong>-<br />
<strong>Collection</strong> die Kommunikation zwischen verschiedenen Modulen. Damit reicht es für<br />
die Simulation aus, wenn der erste Thread lokale Operationen innerhalb einzelner Objekte<br />
simuliert. Er löscht zufällige Referenzen aus beliebigen ComputingPages. Die<br />
Aufgabe des zweiten Thread ist die Simulation des <strong>Garbage</strong>Collectors. Er führt die Traversierung<br />
durch und markiert erreichbare Objekte. Nach Ende des Traversierung werden<br />
beide Threads gestoppt. Mit Hilfe eines weiteren Algorithmus zur Traversierung<br />
wird überprüft, ob tatsächlich alle erreichbaren Objekte als solche markiert wurden.<br />
Zur Evaluation der Funktionalität ist es notwendig, verschiedene Objektgraphen zu<br />
generieren. Die generierten Objektgraphen müssen ausreichend allgemein sein, um alle<br />
vorkommenden Fälle zur Traversierung abzudecken. Darüberhinaus sollte die generierte<br />
Struktur gewisse Bedingungen erfüllen, die es erlauben, die möglichen Problemfälle des
42 KAPITEL 5. ANALYSE DES HW-GC-ALGORITHMUS<br />
Algorithmus aufzuspüren. Für aussagekräftige Tests muss die konkrete Struktur zufällig<br />
generiert werden. Die Java-Umgebung verfährt dabei nach folgendem Algorithmus:<br />
1. Zuerst wird eine einstellbare Anzahl von Knoten zufällig aus den vorhandenen 16<br />
ComputingPages ausgewählt und als Wurzelknoten deklariert.<br />
2. Für jeden Knoten wird eine ebenfalls einstellbare Menge an Referenzen auf andere<br />
Knoten generiert und abgespeichert. Werden in diesem Schritt mehrere gleiche<br />
Referenzen erzeugt, wird die zweite generierte Referenz ignoriert. Für die<br />
<strong>Garbage</strong>-<strong>Collection</strong> ist nur relevant, dass der andere Knoten erreichbar ist, nicht<br />
jedoch über wieviele Referenzen. Damit wird zudem erreicht, das die Anzahl der<br />
Referenzen nicht konstant ist, sondern von eins (wenn immer die gleiche Referenze<br />
generiert wurde) bis zu dem eingestellten Maximum an Referenzen (wenn<br />
alle generierten Referenzen unterschiedlich sind) variiert.<br />
Um <strong>Garbage</strong>-<strong>Collection</strong>-Algorithmen optimal zu testen, sind die beiden Parameter<br />
“Anzahl der Wurzelknoten” (#W urzelKnoten) und “Anzahl an Referenzen in einem<br />
Knoten” (#KnotenReferenzen) nach zwei Merkmalen optimiert: Der Anzahl der erreichbaren<br />
Knoten und der Anzahl der Kanten im Graph.<br />
Die Anzahl der erreichbaren Knoten sollte möglichst maximal sein. Für die Traversierung<br />
im Mark-Prozess sind nur die erreichbaren Knoten ausschlaggebend, die übrigen<br />
werden in diesem Schritt ignoriert. Ist nur ein einzelner Knoten (der Wurzelknoten) erreichbar,<br />
gibt es keine ausgehenden Kanten. Besteht der Graph aus zwei erreichbaren<br />
Knoten, kann er zwei verschiedene Formen haben. Weil einer der beiden Knoten ein<br />
Wurzelknoten ist und der andere Knoten von der Wurzel aus erreichbar ist, muss eine<br />
Kante von der Wurzel zum zweiten Knoten existieren. Die zwei möglichen Objektgraphen<br />
unterscheiden sich darin, ob auch eine Kante vom zweiten Knoten zur Wurzel<br />
zurückverweist. Die Anzahl der möglichen Objektgraphen lässt sich allgemein berechnen.<br />
In einem Graph mit n Objekten gibt es maximal ∑ n<br />
i=0<br />
n∗(n−1)<br />
2<br />
gerichtete Kanten.<br />
Es ist offensichtlich, dass eine große Anzahl von erreichbaren Knoten auch eine Vielzahl<br />
verschiedener möglicher Kantenkombinationen garantiert. Diese Vielzahl ist in der<br />
Simulation wünschenswert um ein möglichst breites Spektrum an Testfällen abzudecken.<br />
Um die Anzahl von erreichbaren Knoten in einem zufällig generierten Graphen zu<br />
beeinflussen, bietet sich die Modifikation der Anzahl von Referenzen innerhalb eines<br />
Knotens an. Enthält jedes Objekt mehr Referenzen, dann weist der Objektgraph mit<br />
großer Wahrscheinlichkeit viele erreichbare Knoten auf. Abbildung 5.1 veranschaulicht<br />
diesen Sachverhalt für vier Knoten. Während es mit nur einer ausgehenden Kante pro<br />
Knoten eine Vielzahl von Kombinationen gibt, bei der mindestens einer der Knoten<br />
nicht erreichbar ist, so gibt es für zwei ausgehende Kanten nur noch eine einzige Kombination<br />
sämtlicher Kanten, damit ein Objekt unerreichbar bleibt. Besitzt jeder Knoten
5.2. SOFTWAREBASIERTES SIMULATIONSMODELL 43<br />
Eine Kante pro Knoten<br />
Zwei Kanten pro Knoten<br />
Drei Kanten pro Knoten<br />
Eine Kante pro Knoten<br />
Zwei Kanten pro Knoten<br />
Abbildung 5.1: Objektgraphen mit verschiedener Anzahl an ausgehenden Kanten<br />
sogar drei ausgehende Kanten, so gibt es nur noch eine einzige Kombination der Kanten.<br />
Der Graph ist in diesem Fall vollständig; alle Objekte sind erreichbar.<br />
Zur Generierung einer möglichst geeigneten Objektstruktur wird also in einem ersten<br />
Schritt evaluiert, bei welchen Parametern nach der Generierung sämtliche Knoten vom<br />
Wurzelknoten aus erreichbar sind. Diese Bedingung wird von einem breiten Bereich<br />
möglicher Parameter erfüllt.<br />
Um eine maximale Erreichbarkeit aller Knoten zu gewährleisten, wäre es am einfachsten,<br />
einen vollständigen Graphen anzulegen. Ein vollständiger Graph enthält alle Kanten,<br />
die möglich sind. Jeder Knoten in diesem Graphen ist auch erreichbar. Zur Simulation<br />
bietet der vollständige Graph jedoch einen gravierenden Nachteil: Wie man in<br />
Abbildung 5.1 sehen kann, hat das Entfernen einer einzelnen Kante keinen Einfluss auf<br />
die Erreichbarkeit. Für die Evaluation der nebenläufigen <strong>Garbage</strong>-<strong>Collection</strong> sind jedoch<br />
insbesondere solche Fälle interessant, bei denen das Löschen einer Kante (durch<br />
den Mutator) die Objektstruktur ändert. Diese Modifikation während eines aktiven GC-<br />
Zyklus zeichnet einen nebenläufigen Mechanismus aus und ist Ziel der Softwareevaluation.
44 KAPITEL 5. ANALYSE DES HW-GC-ALGORITHMUS<br />
Ein Graph, in dem das Löschen jeder beliebigen Kante Einfluss auf die Erreichbarkeit<br />
der Objekte hat, ist der minimale aufspannende Graph [20]. Dieser Graph besitzt<br />
für n Knoten eine minimale Anzahl von Kanten, so dass dabei immer noch sämtliche<br />
Knoten erreichbar sind. Hierfür werden n − 1 Kanten benötigt. Ein möglicher minimal<br />
aufspannender Graph für vier Knoten ist in Abbildung 5.2 angegeben.<br />
Abbildung 5.2: Minimaler aufspannender Graph<br />
Der minimal aufspannende Graph entsteht, wenn ein Minimum an Kanten in jedem<br />
Knoten erzeugt werden. Diese Forderung nach möglichst wenig Kanten steht im Gegensatz<br />
zur Forderung nach möglichst vielen Kanten, um die Erreichbarkeit aller Knoten<br />
zu gewährleisten.<br />
Es gilt also durch statistische Messungen in der Simulation festzustellen, welche Parameter<br />
zu Generierung des Objektgraphen zu wählen sind, so dass zwar alle 16 ComputingPages<br />
erreichbar sind, die Anzahl der Kanten im Graphen jedoch so klein wie<br />
möglich ist. Die Anzahl der Kanten dient dabei als heuristisches Maß für die “Minimalität”<br />
des Graphen. Es kann nicht sichergestellt werden, dass das Löschen einer Kante in<br />
einem Graphen mit weniger Kanten tatsächlich häufiger die Objektstruktur ändert, als<br />
dies in einem Graphen mit mehr Kanten der Fall ist. Um eine hinreichend große Menge<br />
verschiedener Objektgraphen zu erstellen, ist diese Näherung jedoch ausreichend.<br />
In Abbildung 5.3 sind die Parameter für die Anzahl der erreichbaren Knoten aufgetragen.<br />
Die vier Kurven geben die Anzahl der Wurzelknoten an, die Ergebnisse sind<br />
jeweils über 50 Messreihen gemittelt. Man sieht, dass sich die Kurven ab zwei Referenzen<br />
kaum noch unterscheiden. Das ist darin begründet, dass in diesem Fall mit weitere<br />
Wurzelknoten mit großer Wahrscheinlichkeit auf einem bereits erreichbaren Knoten<br />
zum Liegen kommen.<br />
Wegen der hohen Ähnlichkeit der einzelnen Kurven beschränken wir uns im weiteren<br />
auf Graphen mit zwei Wurzeln. Eine einzige Wurzel würde zwar ebenfalls ausreichen,<br />
um jedoch die Option zu besitzen, Konzepte auch für mehrere die Realisierung mehrerer<br />
Threads zu überprüfen, fiel die Entscheidung auf einen Graphen mit zwei Wurzelknoten.
5.3. QUALITATIVE BETRACHTUNG 45<br />
ErreichbareKnoten<br />
16<br />
14<br />
12<br />
10<br />
8<br />
6<br />
4<br />
2<br />
0<br />
Eine Wurzel<br />
Zwei Wurzeln<br />
Drei Wurzeln<br />
Vier Wurzeln<br />
0 1 2 3 4 5 6 7<br />
KnotenReferenzen<br />
Abbildung 5.3: Erreichbare Knoten in Zufallsgraphen<br />
5.3 Qualitative Betrachtung<br />
Kernstück einer qualitativen Betrachtung der entwickelten Algorithmen muss eine Untersuchung<br />
ihrer korrekten Funktionalität sein. Diese besteht bei der <strong>Garbage</strong>-<strong>Collection</strong><br />
aus vier verschiedenen Aspekten, die im Zusammenspiel ein funktionierendes System<br />
ausmachen.<br />
Theorem 5.3.1 (Funktionalität der <strong>Garbage</strong>-<strong>Collection</strong>)<br />
- Der Algorithmus muss korrekt sein (Siehe Definition 3.0.1).<br />
- Der Algorithmus muss partiell vollständig sein (Siehe Definition 4.4.1).<br />
- Bei Bedarf muss der nächste GC-Zyklus gestartet werden (Anlauf-Garantie).<br />
- Ein GC-Zyklus muss terminieren.<br />
Zusätzlich zu den beiden Begriffen der Korrektheit und partiellen Vollständigkeit werden<br />
also zwei Forderungen bezüglich der Laufzeit der <strong>Garbage</strong>-<strong>Collection</strong> gestellt.<br />
Die Anlaufgarantie gewährleistet, dass eine Anwendung solange Speicher allozieren<br />
kann, wie noch welcher verfügbar ist. Ohne diese Forderung wäre ein Implementierung<br />
denkbar, die zwar einen absolut korrekten GC-Mechanismus beinhaltet, ihn jedoch niemals<br />
verwendet. Belegter Speicher wird somit nicht mehr freigegeben.
46 KAPITEL 5. ANALYSE DES HW-GC-ALGORITHMUS<br />
Die Terminierung des Algorithmus fordert eine endliche Laufzeit der <strong>Garbage</strong>-<strong>Collection</strong>.<br />
Würde der Algorithmus nicht terminieren, könnte man einen Algorithmus entwickeln<br />
der unbegrenzt lange für die Traversierung der Daten benötigt. Obwohl solch ein<br />
Algorithmus durchaus korrekt und vollständig sein könnte, ist er für die Praxis untauglich.<br />
Es wäre wünschenswert, die Gültigkeit der Forderungen 5.3.1 mit Hilfe der Methoden<br />
der formalen Verifikation zu beweisen. Allerdings verwenden solche Verifikationsverfahren<br />
üblicherweise eine funktionale Sprache, um die zu verifizierenden Aussagen zu<br />
formulieren. Um Aussagen bezüglich anderer Sprachen (wie in unserem Fall VHDL)<br />
zu treffen, ist es entweder erforderlich, die Algorithmen noch einmal neu zu formulieren,<br />
oder eine Übersetzung aus der Implementierungssprache in die Verifikationssprache<br />
durchzuführen. Beide Ansätze sind komplex [11] und fehleranfällig, so dass sie den<br />
Rahmen der vorliegenden Arbeit sprengen würden. Deswegen beschränkt sich die folgende<br />
qualitative Analyse des HW-GC-Algorithmus auf einen halb-formellen Beweis.<br />
Die Argumentation ist in den meisten Fällen direkt einsichtig und erfordert keine weiteren<br />
formalen Hilfen.<br />
Der Beweis wird nach den einzelnen Prozessen der <strong>Garbage</strong>-<strong>Collection</strong> aufgeteilt. Für<br />
jeden dieser Teile wird gezeigt, dass die Forderungen aus Theorem 5.3.1 nicht verletzt<br />
werden. Diese einzelnen Prozesse der <strong>Garbage</strong>-<strong>Collection</strong> sind:<br />
1. Warten auf den nächsten <strong>Garbage</strong>-<strong>Collection</strong>-Zyklus<br />
2. Die Traversierung im Mark-Prozess<br />
3. Der Sweep-Prozess<br />
5.3.1 Warten auf den nächsten <strong>Garbage</strong>-<strong>Collection</strong>-Zyklus<br />
In dieser Phase ist die <strong>Garbage</strong>-<strong>Collection</strong> im Ruhezustand. Es findet der normale Programmablauf<br />
statt. Die einzelnen ComputingPages können über den Arbiter miteinander<br />
kommunizieren.<br />
Je nach Implementierung sind für die Anlauf-Garantie zwei unterschiedliche Situationen<br />
denkbar. Wenn der nächste GC-Zyklus nach endlicher Zeit automatisch wieder<br />
startet, ist diese Forderung trivialerweise erfüllt. In diesem Fall kann sogar von einer<br />
Terminierung dieses Teilschritts gesprochen werden.<br />
Es sind jedoch Implementierungen der <strong>Garbage</strong>-<strong>Collection</strong> denkbar, bei denen der<br />
nächste Bereinigungsvorgang erst dann startet, wenn die Anforderung eines weiteren,<br />
freien Speicherbereichs nicht mehr erfüllt werden kann. Ein GC-Zyklus wird also nur<br />
ausgeführt, wenn das Freigeben von nicht mehr benötigtem Speicher unbedingt notwendig<br />
ist, um den weiteren Programmablauf zu gewährleisten.<br />
Beim Ablauf eines Programms, dass nur konstanten Speicher benötigt, wird somit niemals<br />
ein GC-Zyklus gestartet. Eine garantierte Terminierung dieses Schrittes ist damit
5.3. QUALITATIVE BETRACHTUNG 47<br />
nicht mehr gegeben, weshalb die Anlauf-Garantie als ein “verallgemeinerter” Terminierungsbegriff<br />
eingeführt wurde.<br />
Die HW-GC-Implementierung verwendet einen einfachen Zähler, um den nächsten<br />
<strong>Garbage</strong>-<strong>Collection</strong>-Zyklus zu aktivieren. Offensichtlich erfüllt dieser Ansatz die Anlauf-<br />
Garantie: Wird freier Speicher angefordert, ohne dass dieser verfügbar ist, so existiert<br />
eine definierte Zeitschranke, innerhalb derer der nächste GC-Zyklus angestoßen wird.<br />
Die Forderungen nach Korrektheit und Vollständigkeit können hier ignoriert werden.<br />
Da der eigentliche Mark-Sweep-Prozess noch nicht eingesetzt hat, lassen sich auch noch<br />
keine Aussagen bezüglich dieser beiden Faktoren treffen. Die Forderung nach Terminierung<br />
bezieht sich ebenfalls nur auf den eigentlichen Mark-Sweep-Vorgang. Sie kann<br />
in diesem Schritt ebenfalls ignoriert werden.<br />
Damit gilt:<br />
Theorem 5.3.2<br />
Die Forderungen aus Theorem 5.3.1 werden beim Warten auf den nächsten GC-Zyklus<br />
nicht verletzt.<br />
5.3.2 Die Traversierung im Mark-Prozess<br />
Der Mark-Prozess erfordert den aufwändigsten Beweis. Das ist im Wesentlichen in der<br />
komplexen Struktur der Traversierung begründet.<br />
Wir beginnen den Beweis der Forderungen aus Theorem 5.3.1 mit dem Nachweis der<br />
Korrektheit. Die Korrektheit fordert, dass ein Objekt nur dann freigegeben wird, wenn<br />
es nicht mehr erreichbar ist. Dazu muss gezeigt werden, dass alle Referenzen, die in<br />
einem Objekt abgelegt sind, tatsächlich dazu genutzt werden, die zugehörigen Module<br />
zu aktivieren. Alle Referenzen sind in zwei Listen abgelegt. Diese Listen werden beide<br />
vollständig der Reihe nach abgearbeitet. Die Adressen der einen Liste werden dabei zu<br />
Modulen mit kleineren, die der anderen zu denen mit größeren Adressen geschickt. Die<br />
Implementierung stellt sicher, dass die Referenzen bereits beim Abspeichern der richtigen<br />
Liste zugeteilt werden. Es bleibt zu zeigen, dass die Einträge aus den Referenzlisten<br />
auch gesendet werden und nicht etwa ein Deadlock oder ähnliches auftritt. Ohne<br />
Beschränkung der Allgemeinheit wird im folgenden der auf dem FPGA von rechts nach<br />
links laufende Bus betrachtet. Liegt am rechten Eingang ein gültiges Signal an, so hat<br />
dieses eine höhere Priorität und wird zuerst an den linken Ausgang angelegt. Um sicher<br />
zu stellen, dass alle eigenen Referenzen gesendet werden, muss also gezeigt werden,<br />
dass es eine obere Schranke für die Anzahl der Adressen am rechten Eingang gibt.<br />
Eine solche Schranke existiert, weil rechts von der betrachtete ComputingPage nur<br />
endlich viele weitere Module liegen, die wiederum nur eine endliche Anzahl von Referenzen<br />
enthalten. Es ist also möglich, die eigenen Referenzen in endlicher Zeit abzuarbeiten.
48 KAPITEL 5. ANALYSE DES HW-GC-ALGORITHMUS<br />
Der Beweis der Terminierung ist eng verwandt mit dem Beweis zur Korrektheit. Wie<br />
bereits gezeigt wurde, existieren auf beiden Seiten einer beliebigen ComputingPage nur<br />
endlich viele weitere Module. Deswegen lässt sich eine zeitliche Obergrenze angeben,<br />
nach der alle dort enthaltenen Referenzen, abgearbeitet worden sind. Damit existiert<br />
natürlich auch für die gesamte Plattform eine zeitliche Obergrenze zur Abarbeitung<br />
aller Referenzen aus allen Modulen.<br />
Für die partielle Vollständigkeit ist eine Betrachtung der Nebenläufigkeit erforderlich.<br />
Für den Fall, das die Referenzen während des gesamten Mark-Prozesses konstant sind,<br />
ist diese Forderung offensichtlich erfüllt. Während der Traversierung können Referenzen<br />
gelöscht werden. Falls dabei der Objektgraph nicht geändert wird, gilt offensichtlich<br />
immer noch die Vollständigkeit. Wird ein Objekt durch den Löschvorgang unerreichbar,<br />
so gilt zu unterscheiden: Wurden diese Referenzen noch nicht abgearbeitet, gibt es<br />
keine weiteren Auswirkungen. Der Objektgraph kann betrachtet werden, als wäre die<br />
Referenz schon seit Beginn der Traversierung entfernt. Wird eine Referenz hingegen<br />
erst gelöscht, nachdem sie bereits abgearbeitet wurde, wird sie im aktuellen GC-Zyklus<br />
noch als markiert betrachtet und kann nicht freigegeben werden. Nach Voraussetzung<br />
ist das ehemals referenzierte Objekt jetzt unerreichbar. Es kann zu keinem zukünftigen<br />
Zeitpunkt mehr erreichbar sein und wird deshalb im nächsten GC-Zyklus erkannt und<br />
freigegeben.<br />
Die Forderung der Anlauf-Garantie ist trivial erfüllt, weil im Mark-Prozess die <strong>Garbage</strong>-<strong>Collection</strong><br />
ja bereits angelaufen ist.<br />
Damit gilt:<br />
Theorem 5.3.3<br />
Die Forderungen aus Theorem 5.3.1 werden während der Traversierung im Mark-Prozess<br />
nicht verletzt.<br />
5.3.3 Der Sweep-Prozess<br />
Für den Sweep-Prozess gilt, dass die Anlauf-Garantie und Terminierung offensichtlich<br />
gelten. In diesem Prozess wird ein Shift-Register, dessen Breite identisch mit der Anzahl<br />
der ComputingPages ist, einmal vollständig seriell ausgelesen. Weil die Anzahl der<br />
ComputingPages endlich ist, ist damit auch das Auslesen endlich. Der Prozess terminiert<br />
somit. Da die <strong>Garbage</strong>-<strong>Collection</strong> noch immer aktiviert ist, ist die Anlauf-Garantie<br />
mit der gleichen Argumentation wie im Mark-Prozess erfüllt.<br />
Auf die Forderungen der Korrektheit und partiellen Vollständigkeit hat der Sweep-<br />
Prozess keinen Einfluss. In diesem Schritt werden in dem Sweep-Register die Flags<br />
ausgelesen, die während des Mark-Prozesses gesetzt worden sind. Wird eine Null gelesen,<br />
so ist das korrespondiere Objekt bei der Traversierung nicht aktiviert worden. Eine<br />
Eins bedeutet, dass das Objekt vom Wurzelknoten aus über Referenzen erreichbar ist.<br />
Der Sweep-Schritt liest diese Markierungen nur aus, modifiziert sie jedoch nicht. Die
5.4. QUANTITATIVE BETRACHTUNG 49<br />
Forderungen zur Korrektheit und partiellen Vollständigkeit sind also erfüllt, solange sie<br />
zu Beginn des Sweep-Prozess erfüllt waren.<br />
Theorem 5.3.4<br />
Die Forderungen aus Theorem 5.3.1 werden während des Sweep-Prozesses nicht verletzt.<br />
5.3.4 Funktionalität des HW-GC-Algorithmus<br />
Mit den Theoremen 5.3.2, 5.3.3 und 5.3.4 ist damit Theorem 5.3.1 nachgewiesen. Der<br />
implementierte Algorithmus erfüllt also die Aufgaben einer automatischen Speicherverwaltung.<br />
Dieser informelle Beweis trifft nur Aussagen bezüglich der textuellen Spezifikation<br />
des Algorithmus. Ein Beweis, dass die konkrete Implementierung (als VHDL-Code<br />
oder gar als Schaltbild) tatsächlich die Spezifikation erfüllt, würde den Rahmen dieser<br />
Arbeit sprengen.<br />
5.4 Quantitative Betrachtung<br />
Die quantitative Analyse eines Algorithmus versucht Aussagen bezüglich seiner Leistungsfähigkeit<br />
zu treffen. Es wird eine Metrik benötigt, in der solche Aussagen getroffen<br />
werden können. Dabei kann es sich entweder um eine relative Metrik handeln, die vergleichende<br />
Aussagen erlaubt. Die Aussage “Der Algorithmus A ist doppelt so schnell,<br />
wie Algorithmus B” wäre ein Beispiel hierfür. Der Vorteil einer solchen Methodik ist<br />
die Möglichkeit verschiedene Algorithmen ohne die Kenntnis ihrer Arbeitsweise vergleichen<br />
zu können. Es reicht aus, sie als “Black-Box”-System zu betrachten und zu<br />
messen. Allerdings lassen sich solche vergleichende Metriken nicht ohne Weiteres verallgemeinern.<br />
Schon durch die Verwendung einer anderen Hardwareplattform sind neue<br />
Erkenntnisse nicht mehr ohne Weiteres mit den alten Ergebnissen vereinbar.<br />
Günstiger ist die Entwicklung einer allgemeinen Metrik, die von einem konkreten Algorithmus<br />
abstrahiert und Merkmale findet, die für alle Algorithmen einer Klasse bestimmbar<br />
sind. Diese Merkmale lassen sich direkt messen und erlauben auch einen Vergleich<br />
zwischen verschiedenen Untersuchungen. Eine Metrik soll so allgemein gefasst<br />
sein, dass sie vielseitig anwendbar ist. Die Schwierigkeit liegt darin, sinnvolle Metriken<br />
zu finden.<br />
Die bekannteste Metrik ist die Laufzeitmessung. Durch Angabe der Laufzeit in Sekunden<br />
lassen sich beliebige Algorithmen miteinander vergleichen. Um einen präziseren<br />
Vergleich zu ermöglichen, ist es sinnvoll, speziellere Metriken zu entwerfen. So ist<br />
beim Vergleich zweier Sortieralgorithmen nicht nur die gesamte Laufzeit von Interesse.<br />
Ebenso wichtig ist es zu bestimmen, wie viele Speicherzugriffe erforderlich sind und
50 KAPITEL 5. ANALYSE DES HW-GC-ALGORITHMUS<br />
wieviel Zeit in der Ein/Ausgabe verbracht wird. Es sind also Metriken notwendig, die<br />
Vergleichsmöglichkeiten zwischen möglichst vielen verschiedenen Implementierungen<br />
bieten, gleichzeitig aber auf die wichtigen Aspekte der Algorithmen eingehen. Im Folgenden<br />
werden solche Metriken für die <strong>Garbage</strong>-<strong>Collection</strong> entwickelt. Sie berücksichtigen<br />
insbesondere den Einfluss der Nebenläufigkeit, die sich im Ablauf der Traversierung<br />
und in der gleichzeitigen Ausführung von Mutator und Collector niederschlägt.<br />
5.4.1 Laufzeit der Traversierung<br />
Maßgeblich für die Laufzeit eines vollständigen GC-Zyklus ist die Laufzeit der Traversierung<br />
im Mark-Prozess. Um die Leistungsfähigkeit einer Graphentraversierung zu<br />
beurteilen bietet es sich an, anzugeben, wieviel Verarbeitungsschritte sie erfordert. Dieser<br />
Wert hängt von der Struktur der Knoten und der Struktur der Kanten ab.<br />
Der Sweep-Prozess erfordert eine einmalige lineare Traversierung aller Objekte. Für<br />
n Objekte erfordert er somit n Schritte. Der Aufwand für den Mark-Prozess hängt hingegen<br />
vom Objektgraphen ab. Für n ′ erreichbare Objekte, die im Mittel m Referenzen<br />
auf weitere Objekte enthalten, erfordert ein einfacher sequenzieller Ansatz zur Traversierung<br />
bereits m∗n ′ Schritte. Es ist anzumerken, dass es höchstens so viele erreichbare<br />
Objekte gibt, wie insgesamt vorhanden sind. Es ist also n ′ ≤ n. Trotzdem ist der Mark-<br />
Prozess wegen des Einflusses der Referenzen im Normalfall deutlich aufwändiger.<br />
Metrik 5.4.1 (Laufzeit-Effizienz)<br />
Die Laufzeit eines Algorithmus zur <strong>Garbage</strong>-<strong>Collection</strong> wird maßgeblich von der Laufzeit<br />
des Mark-Prozess beeinflusst. Seine Effizienz lässt sich durch die Anzahl der Verarbeitungschritte<br />
angeben, die notwendig sind, um alle erreichbaren Objekte zu besuchen.<br />
Wie bereits erwähnt, wird bei einer sequenziellen Traversierung aller Knoten eine Liste<br />
von Referenzen mitgeführt. In jedem Verarbeitungsschritt muss geprüft werden, ob<br />
eine Referenz aus dieser Liste zu einem Knoten führt, der bisher noch nicht besucht wurde.<br />
Ist das der Fall, werden diese Referenzen in die Liste aufgenommen. Der Algorithmus<br />
erfordert also m Verarbeitungsschritte. Alle Referenzen, die im Graphen erreichbar<br />
sind, müssen abgearbeitet werden.<br />
Satz 5.4.1 (Sequenzielle Traversierung)<br />
Die sequenzielle Traversierung eines Objektgraphen mit m erreichbaren Kanten benötigt<br />
m Verarbeitungsschritte.<br />
Bearbeitet man alle Referenzen eines Knotens gleichzeitigt, so erhält man eine parallele<br />
Traversierung des Objektgraphen. In Abbildung 4.2 ist eine solche illustriert. Man<br />
erkennt, dass in jedem Bearbeitungschritt alle Objekte in einer bestimmten Entfernung<br />
zum Wurzelknoten bearbeitet werden. Die Laufzeit ist also durch den längsten Pfad von
5.4. QUANTITATIVE BETRACHTUNG 51<br />
einem Wurzelknoten zu einem anderen erreichbaren Knoten bestimmt. In einer Worst-<br />
Case-Betrachtung ist die Länge dieses Pfades mit der Menge aller erreichbaren Objekte<br />
n ′ identisch. Dabei sind alle Objekte in einer Reihe angeordnet und enthalten jeweils<br />
die Referenz auf ihren nächsten Nachbarn. Üblicherweise ist der längste Pfad deutlich<br />
kleiner als n ′ . Jedes erreichbare Objekt erfordert mindestens eine darauf verweisende<br />
Kante, weil es sonst nicht zu erreichen wäre. Damit gilt, das n ′ ≤ m ist und somit<br />
eine parallele Traversierung mit weniger Verarbeitungsschritten als eine sequenzielle<br />
Traversierung ablaufen kann.<br />
Satz 5.4.2 (Parallele Traversierung)<br />
Die parallele Traversierung eines Objektgraphen mit n ′ erreichbaren Knoten und m<br />
Kanten benötigt k Verarbeitungsschritte. k ist die maximale Entfernung eines erreichbaren<br />
Objektes von einer Wurzel. Es gilt k ≤ n ′ ≤ m.<br />
Die realisierte Traversierung aus dem HW-GC-Algorithmus ist in ihrer Leistungsfähigkeit<br />
zwischen der sequenziellen und parallelen Traversierung anzusiedeln. Mögliche<br />
Kollisionen auf dem implementierten Bus-System haben großen Einfluss auf die Traversierung.<br />
Eine ComputingPage kann möglicherweise keine eigenen Daten (Aktivierungen) versenden,<br />
weil es die Signale eines anderen Objekts weiterleiten muss. Betrachtet man den<br />
Extremfall, dann treten jedesmal Kollisionen auf, solange noch andere Objekte Daten<br />
versenden. Ein Objekt kann also erst dann seinen Daten auf den Bus schreiben, wenn<br />
die Objekte mit höherer Priorität ihre Referenzlisten abgearbeitet haben. Man erhält ein<br />
sequenzielles Traversierungsschema. Wenn umgekehrt der Objektgraph so aufgebaut<br />
ist, dass es in keinem Fall zu einer Kollision der Busressourcen kommt, dann können<br />
alle aktivierten Objekte gleichzeitig ihre Referenzen abarbeiten und man erhält eine<br />
Traversierungsreihenfolge, die mehr der parallelen Traversierung ähnelt.<br />
Satz 5.4.3 (HW-GC Traversierung)<br />
Die Traversierung im HW-GC-Algorithmus benötigt maximal so viele Schritte wie die<br />
sequenzielle Traversierung. Sie benötigt mindestens genau so viele Verarbeitungsschritte<br />
wie die parallele Traversierung.<br />
Inwieweit der implementierte HW-GC-Algorithmus eher einer sequenziellen oder parallelen<br />
Traversierung ähnelt, hängt von der jeweiligen Objektstruktur ab. Nach Abschnitt<br />
5.1 lassen sich keine verlässlichen Aussagen über diese Struktur treffen. Es muss<br />
daher bei dieser Abschätzung der Leistungsfähigkeit bleiben. Es lässt sich jedoch festellen,<br />
dass die Anzahl der Kollisionen von dem Verhältnis Kanten abhängt und durch eine<br />
Knoten<br />
Umsortierung der Objekte beeinflusst werden kann.
52 KAPITEL 5. ANALYSE DES HW-GC-ALGORITHMUS<br />
1<br />
a<br />
1<br />
a<br />
1<br />
a<br />
2<br />
6<br />
2<br />
3<br />
2<br />
2<br />
3<br />
5<br />
7<br />
4 5<br />
6<br />
3<br />
3 3<br />
4<br />
b<br />
7<br />
b<br />
4<br />
b<br />
Sequenzielle Traversierung<br />
mit Tiefensuche<br />
Sequenzielle Traversierung<br />
mit Breitensuche<br />
Nebenläufige Traversierung<br />
Abbildung 5.4: Verschiedene Traversierungen<br />
5.4.2 PageMiss<br />
Wird während der <strong>Garbage</strong>-<strong>Collection</strong> eine Kante gelöscht, die bereits traversiert wurde,<br />
besteht die Möglichkeit, dass ein Objekt als aktiv markiert ist, obwohl es tatsächlich<br />
nicht mehr erreichbar ist. Solche Objekte werden erst im nächsten GC-Zyklus korrekt<br />
als <strong>Garbage</strong> erkannt. Diese Situation wird als PageMiss bezeichnet, weil die ComputingPage<br />
bei der Zusammenstellung von nicht mehr benötigten Objekten verfehlt wird.<br />
Die Häufigkeit von PageMisses liefert eine aussagekräftige Metrik zur Analyse einer<br />
nebenläufigen <strong>Garbage</strong>-<strong>Collection</strong>. Weil ein vom PageMiss betroffenes Objekt erst im<br />
folgenden Zyklus freigegeben wird, steigt die mittlere Laufzeit zum Freigeben eines<br />
Objektes an.<br />
Metrik 5.4.2 (PageMiss-Effizienz)<br />
In einem nebenläufigen GC-Algorithmus kann es dazu kommen, dass ein Objekt innerhalb<br />
eines GC-Zyklus als aktiv markiert bleibt, obwohl es bereits nicht erreichbar ist.<br />
Diese Situation wird als PageMiss bezeichnet und ist ein Maß für die Leistungsfähigkeit<br />
des Algorithmus.<br />
Gibt eine Implementierung beispielsweise sämtliche Objekte erst nach zwei Durchläufen<br />
frei, so hätte sich die Laufzeit, bis ein Objekt korrekt als <strong>Garbage</strong> erkannt wird,<br />
gegenüber der Zeit für einen GC-Zyklus verdoppelt. Tritt umgekehrt in einer Implementierung<br />
für kein Objekt ein PageMiss auf, so ist die mittlere Laufzeit zur Freigabe eines<br />
Objekts identisch mit der Laufzeit eines GC-Zyklus.<br />
Für die Anzahl der PageMisses spielt die gewählte Traversierungsreihenfolge eine entscheidende<br />
Rolle. In Abbildung 5.4 sind der Objektgraph und seine Traversierung mit<br />
drei verschiedenen Verfahren dargestellt. Bei der Tiefen- und der Breitensuche handelt<br />
es sich um sequenzielle Verfahren. Die parallele Suche kann mehrere Knoten zugleich<br />
verarbeiten.<br />
Angenommen ein Mutator löscht zum Zeitpunkt 4 die, mit ’a’ markierte Kante. Zum<br />
Zeitpunkt 5 befindet sich die Traversierung im entsprechend mit 5 markiertem Knoten.
5.4. QUANTITATIVE BETRACHTUNG 53<br />
Knoten, die mit einer kleineren Zahl markiert sind, wurden bereits besucht, Knoten<br />
mit höherer Zahl noch nicht. Nach der Traversierung entstehen die in Abbildung 5.5<br />
aufgeführten Objektgraphen. Die gestrichelten Linien markieren Teile, die immer noch<br />
aktiv markiert sind, obwohl sie tatsächlich bereits nicht mehr erreichbar sind. Es fällt<br />
auf, dass Breitensuche und Tiefensuche zu unterschiedlichen Ergebnissen gelangt sind.<br />
Dies ist insofern überraschend, als die beiden in einem nicht nebenläufigen Algorithmus<br />
bezüglich Funktionalität und Laufzeit identisch sind.<br />
1<br />
1<br />
a<br />
1<br />
a<br />
2<br />
2<br />
3<br />
2<br />
2<br />
3<br />
5<br />
4 5<br />
6<br />
3<br />
3 3<br />
b<br />
b<br />
b<br />
4<br />
7<br />
4<br />
Sequenzielle Traversierung<br />
mit Tiefensuche<br />
Sequenzielle Traversierung<br />
mit Breitensuche<br />
Nebenläufige Traversierung<br />
Abbildung 5.5: Neuer Objektgraph nach löschen der Kante a) zum Zeitpunkt 5<br />
Das geschilderte Beispiel beim Entfernen der Kante ’a’ deutet darauf hin, dass die Tiefensuche<br />
der Breitensuche überlegen ist. Wird im Ausgangsbeispiel statt ’a’ die Kante<br />
’b’ zum Zeitpunkt 4 entfernt, so sieht man, dass auch die Breitensuche ein besseres Ergebnis<br />
erzeugen kann. In Abbildung 5.6 sieht man, dass die Breitensuche den Objektgraph<br />
korrekt erkannt hat. Bei der Breitensuche ist hingegen noch ein Objekt vorhanden,<br />
das bereits nicht mehr erreichbar ist.<br />
1<br />
a<br />
1<br />
a<br />
1<br />
a<br />
2<br />
6<br />
2<br />
3<br />
2<br />
2<br />
3<br />
5<br />
7<br />
4 5<br />
6<br />
3<br />
3 3<br />
4<br />
b<br />
4<br />
b<br />
Sequenzielle Traversierung<br />
mit Tiefensuche<br />
Sequenzielle Traversierung<br />
mit Breitensuche<br />
Nebenläufige Traversierung<br />
Abbildung 5.6: Neuer Objektgraph nach löschen der Kante b) zum Zeitpunkt 4<br />
Allgemein gilt, das die Breitensuche Objekte, die weit unten im Baum stehen, erst später<br />
als die Tiefensuche abarbeitet. Berücksichtigt man die weak generational hypothesis,<br />
die besagt, dass junge Objekte (diese stehen weiter unten im Baum) häufiger gelöscht
54 KAPITEL 5. ANALYSE DES HW-GC-ALGORITHMUS<br />
werden, so ist Wahrscheinlichkeit, dass eine Kante noch vor ihrer Abarbeitung entfernt<br />
wird bei der Breitensuche höher. Die Breitensuche verspricht also, eine geringere Anzahl<br />
von PageMisses zu erzeugen.<br />
Eine Tiefensuche durchläuft nacheinander alle Äste bis zum Ende. Gegenüber der<br />
Breitensuche werden somit häufiger Knoten, die nah an einer Wurzel liegen, erst spät<br />
abgearbeitet. Würde in solch einem Knoten eine Referenz gelöscht, wäre durch diese<br />
einzige Operation ein ganzer Teilbaum nicht mehr erreichbar. Die Breitensuche würde<br />
in diesem Fall eine sehr hohe Anzahl von PageMisses erzeugen.<br />
Es gilt also, bei der Entscheidung zwischen Breiten- und Tiefensuche abzuwägen, ob<br />
das seltene Wegfallen von großen Teilbäumen (bei der Tiefensuche) oder aber das häufige<br />
Wegfallen weniger Objekte (bei der Breitensuche) stärker berücksichtigt werden<br />
soll. Blackburn hat in seinen Untersuchungen gezeigt [22], dass in den meisten Fällen<br />
die Tiefensuche bessere Ergebnisse liefert.<br />
Die parallele Traversierung wurde bisher nicht berücksichtigt. Sie erzeugt in beiden<br />
Beispielen ebensoviele PageMisses, wie die jeweils ungeeignetere sequenzielle Traversierung.<br />
Dies ist jedoch nicht in einer ungünstigen Traversierungsreihenfolge begründet,<br />
sondern in deren hoher Geschwindigkeit. Weil diese Traversierung nur vier Takte benötigt,<br />
ist der GC-Zyklus beim Löschen der Kanten ’a’ und ’b’ bereits abgeschlossen. Deshalb<br />
wäre es eigentlich korrekt, die Kantenoperation bereits dem nächsten GC-Zyklus<br />
zuzuordnen. In diesem Zyklus würde bei beiden Beispielen der tatsächliche Objektgraph<br />
erkannt, ein PageMiss tritt nicht auf.<br />
Wie erläutert, basiert die Traversierung der vorgestellten Implementierung auf der parallelen<br />
Traversierung. Weil Objekte, die nah an der Wurzel stehen, vor Knoten, die weiter<br />
von der Wurzel entfernt stehen, traversiert werden, besitzt sie mehr Ähnlichkeit mit<br />
einer Breitensuche. Das komplexe Verhalten des Bus-Systems, insbesondere seine zeitliche<br />
Verzögerung der Signale mit Registern und die Auflösung von Ressourcenkonflikten,<br />
macht einen direkten Vergleich mit den Verfahren der Tiefen- oder Breitensuche<br />
unmöglich.<br />
Eine schnellere und ressourcenschonende Hardwarekommunikation wurde deshalb<br />
gegenüber den klassischen Traversierungsverfahren bevorzugt. Eine höhere Anzahl von<br />
PageMisses kann durch die häufigere Ausführung der <strong>Garbage</strong>-<strong>Collection</strong> ausgeglichen<br />
werden. Dies ist möglich, weil ein einzelner GC-Zyklus schneller ablaufen kann.<br />
Neben der implementierten Traversierungsreihenfolge lassen sich noch andere Kriterien<br />
finden, die einen Einfluss auf die Zahl der PageMisses haben:<br />
• Ein Kriterium ist das Verhältnis <strong>Garbage</strong>CollectorGeschwindigkeit . Dieser Faktor gibt<br />
MutatorGeschwindigkeit<br />
an, wie schnell der <strong>Garbage</strong> Collector im Vergleich zum Programm arbeitet. Je<br />
größer dieser Wert, desto schneller arbeitet der <strong>Garbage</strong> Collector. Im Idealfall<br />
kann ein GC-Zyklus komplett zwischen zwei Anweisungen des Mutators ausgeführt<br />
werden. Anstelle einer nebenläufigen <strong>Garbage</strong>-<strong>Collection</strong> erhält man wieder<br />
eine einfache, sequenzielle <strong>Garbage</strong>-<strong>Collection</strong>, in der kein PageMiss auftreten
5.5. HÄUFIGKEIT DER GC-ZYKLEN 55<br />
kann.<br />
• Ein weiterer Faktor für die Anzahl der PageMisses ist die Dichte des Graphen.<br />
Ein PageMiss kann nur dann auftreten, wenn durch das Löschen einer Referenz<br />
auch ein Objekt aus dem Objektgraphen entfernt werden kann. Je mehr Wege<br />
es von den Wurzelknoten zu den erreichbaren Knoten gibt, desto seltener wird<br />
beim Löschen einer Referenz tatsächlich ein Objekt unerreichbar. Je mehr Wege<br />
zu den Objekten es gibt, um so dichter ist der Graph. Eine präzise und kompakte<br />
Formulierung der Dichte ist sehr aufwändig. Ein gut geeignetes Maß, das, ist die<br />
Anzahl aller Kanten, die im aktuellen Objektgraph vorkommen. Bei Bedarf kann<br />
diese Maß noch bezüglich der Anzahl der erreichbaren Objekte normiert werden,<br />
AnzahlallerKanten<br />
indem man<br />
bildet. Damit ist die Dichte nicht mehr von der<br />
AnzahlerreichbareObjekte<br />
Größe des betrachtete Objektgraphen abhängig.<br />
• Wird auf der vorgestellten Plattform ein herkömmliches objektorientiertes Programm<br />
implementiert, dann ist pro Thread (d.h. pro Wurzelknoten) nur eine ComputingPage<br />
während eines GC-Laufs aktiv. Weil der Arbiter in dieser Phase Kommunikation<br />
unterbindet, können auch nur Referenzen in diesem Knoten gelöscht<br />
werden. Außerdem können maximal alle Referenzen entfernt werden, die im Knoten<br />
vorher vorhanden waren.<br />
5.5 Häufigkeit der GC-Zyklen<br />
Mit Hilfe der entwickelten Metriken lässt sich die Leistung des implementierten HW-<br />
GC-Algorithmus bestimmen und mit andere Implementierungen vergleichen. Darüber<br />
hinaus ermöglichen sie eine Optimierung der bestehenden Implementierung.<br />
Die Laufzeit eines GC-Zyklus ist weitgehend festgelegt. Sie hängt von der implementierten<br />
Datenstruktur zur Traversierung und der jeweiligen Objektstruktur ab. Neben<br />
kleinen Optimierungen, die möglicherweise das Einsparen einiger Taktzyklen erlauben,<br />
wäre zur Reduzierung der Laufzeit nur noch das Umkopieren der Objekte von Interesse.<br />
Die Kommunikation zwischen zwei Objekten, deren ComputingPages fünf Blöcke<br />
auseinanderliegen, benötigt aufgrund der dazwischenliegenden Register mindestens<br />
fünf Takte. Würde man die beiden Objekte in zwei nebeneinanderliegende ComputingPages<br />
verschieben, wäre eine Kommunikation in nur einem Takt möglich (siehe<br />
Abschnitt 4.3). Diese Art der Optimierung wird aber aufgrund der eingeschränkten<br />
Möglichkeiten aktueller partiell rekonfigurierbarer Architekturen weder kurz- noch mittelfristig<br />
zu realisieren sein.<br />
Ein weiterer Parameter zur Leistungssteigerung ist die Wahl einer optimalen Wartezeit<br />
zwischen zwei GC-Zyklen. Während eines GC-Zyklus ist die Kommunikation über den<br />
Arbiter deaktiviert. Erfordert der Programmablauf in dieser Zeit eine solche Kommunikation,<br />
dann kommt es zu einer Verzögerung: Das Programm muss warten, bis der
56 KAPITEL 5. ANALYSE DES HW-GC-ALGORITHMUS<br />
GC-Zyklus abgeschlossen ist. Um solche Verzögerungen zu vermeiden, ist also eine<br />
möglichst seltene Ausführung der <strong>Garbage</strong>-<strong>Collection</strong> zu empfehlen. Umgekehrt muss<br />
vermieden werden, dass der Programmablauf die Instanziierung weiterer Objekte anfordert,<br />
aber aktuell kein freier Speicherplatz zu Verfügung steht. Auch in diesem Fall<br />
muss das Programm ausgesetzt werden, bis ein GC-Zyklus abgeschlossen ist und freizugebende<br />
Objekte identifiziert hat. In diesem Fall hätte der GC-Zyklus bereits früher<br />
gestartet werden müssen, einen Verzögerung hätte durch eine nebenläufige Implementierung<br />
vermieden werden können.<br />
In der vorgestellten Implementierung wird das Starten der <strong>Garbage</strong>-<strong>Collection</strong> über<br />
eine einfache und konstante Verzögerung realisiert. Diese Methode ermöglicht eine<br />
kompakte und wenig fehleranfällige Implementierung. Während der Entwicklung sind<br />
verschiedene Kriterien entwickelt worden, die in einer Ausbaustufe zum optimierten<br />
Starten der <strong>Garbage</strong>-<strong>Collection</strong> eingesetzt werden können. Diese Kriterien sind im einzelnen:<br />
• Die Anzahl der freien Objekte. Solange noch ausreichend viele freie Objekte vorhanden<br />
sind, ist ein GC-Zyklus nicht erforderlich.<br />
• Der Erfolg der vorherigen GC-Zyklen. Wenn in den letzten Zyklen nur wenige<br />
oder gar keine weiteren freien Objekte gefunden wurden, so wird auch der nächsten<br />
GC-Zyklus wahrscheinlich nicht mehr Speicher freigeben können. In diesem<br />
Fall kann der Start der nächsten <strong>Garbage</strong>-<strong>Collection</strong> nach hinten verschoben werden.<br />
• Wenn die Ausführungszeit der <strong>Garbage</strong>-<strong>Collection</strong> gegenüber der Ausführungszeit<br />
einer Methode des Mutators kurz ist, dann ist auch der Arbiter nur für kurze<br />
Zeit gesperrt. Die Gefahr, den Mutator zu behindern ist damit geringer und die<br />
<strong>Garbage</strong>-<strong>Collection</strong> kann häufiger laufen. Um die Ausführungszeiten zu vergleichen,<br />
ist eine Analyse des Programmablaufs notwendig um zumindest näherungsweise<br />
zu bestimmen, wie oft eine Kommunikation über den Arbiter benötigt wird.
Kapitel 6<br />
Zusammenfassung<br />
6.1 Ergebnisse<br />
In dieser Diplomarbeit wurde die Realisierbarkeit einer automatischen Speicherverwaltung<br />
(<strong>Garbage</strong>-<strong>Collection</strong>) für rekonfigurierbare Hardwarestrukturen untersucht. Die<br />
Speicherverwaltung ist dabei in eine neuartige, objektorientierte Entwurfsmethodik eingebunden,<br />
deren Ziel eine flexiblere Anwendungsentwicklung für Hardware ist.<br />
Zuerst wurden die existierenden Mechanismen zur <strong>Garbage</strong>-<strong>Collection</strong> erarbeitet und<br />
bezüglich ihrer Tauglichkeit für rekonfigurierbare Hardwarestrukturen untersucht. Die<br />
Mark-Sweep-<strong>Collection</strong> hat sich hierbei als günstigstes Konzept erwiesen. Aufbauend<br />
auf dem Basis-Algorithmus wurden die Erweiterungsmöglichkeiten der Mark-Sweep-<br />
<strong>Collection</strong> untersucht. Die Erweiterung zu einem nebenläufigen Algorithmus ist für eine<br />
Umsetzung in Hardware besonders geeignet. Sie steigert die Geschwindigkeit der<br />
<strong>Garbage</strong>-<strong>Collection</strong> erheblich und ist in Hardware einfach zu realisieren.<br />
In einem nächsten Schritt sind die ausgewählten Mechanismen entsprechend der Rahmenbedingungen<br />
einer FPGA-basierten Hardwareumgebung modifiziert und angepasst<br />
worden. Insbesondere für die Traversierung im Mark-Schritt wurden besondere Strukturen<br />
entwickelt, um die Bedingungen der Plattform optimal auszunutzen. Schließlich<br />
wurden die einzelnen Mechanismen zu einem einzigen Algorithmus, dem HW-GC-<br />
Algorithmus, zusammengeführt.<br />
Es wurde gezeigt, dass die Leistungsfähigkeit des Algorithmus von den benötigten<br />
Systemressourcen abhängt, insbesondere den Kommunikationsressourcen. Der vorgestellte<br />
Algorithmus ist auf geringen Ressourcenbedarf ausgerichtet und benötigt nur<br />
zwei zusätzliche globale Signale. Für die übrigen Signale lassen sich die existierenden<br />
Kommunikationsressourcen nutzen, die während der <strong>Garbage</strong>-<strong>Collection</strong> durch den Arbiter<br />
inaktiv geschaltet sind.<br />
Dank der Ausnutzung nebenläufiger Mechanismen bei der Traversierung der Objekte<br />
und dem parallelen Ausführen von Mutator und Collector bleibt die <strong>Garbage</strong>-<strong>Collection</strong>
58 KAPITEL 6. ZUSAMMENFASSUNG<br />
trotzdem hoch performant.<br />
Der Algorithmus wurde mit Hilfe einer Softwaresimulation auf seine Korrektheit geprüft.<br />
Eine informelle Verifikation hat die korrekte Funktionalität des entworfenen Algorithmus<br />
bestätigt.<br />
Die existierenden Methodiken zur Messung der Leistungsfähigkeit haben sich als unzureichend<br />
erwiesen. Sie lassen sich nicht auf den vorgestellten Algorithmus anwenden,<br />
weil sie von klassischen Softwareumgebungen ausgehen.<br />
Um die Leistungsfähigkeit des hardwarebasierten GC-Algorithmus quantitativ beurteilen<br />
zu können, mussten neue Metriken entwickelt werden. Sie erlauben einen Vergleich<br />
des HW-GC-Algorithmus mit anderen Implementierungen und bilden die Grundlage<br />
für weitere Optimierungen des Algorithmus.<br />
6.2 Ausblick<br />
Der vorgestellte Algorithmus wirft weitere Problemstellungen auf, die im Rahmen dieser<br />
Arbeit nicht behandelt werden konnten.<br />
Zum Zeitpunkt der Fertigstellung dieser Arbeit ist die Laufzeitumgebung zur Ausführung<br />
objektorientierter Programme noch nicht abgeschlossen. Der HW-GC-Algorithmus<br />
ist, soweit möglich, in die bestehende Implementierung integriert. Die Ausführung einer<br />
reellen Anwendung ist jedoch noch nicht möglich. Präzisere Aussagen bezüglich der<br />
Leistungsfähigkeit der Implementierung lassen sich treffen, sobald diese Möglichkeit<br />
besteht. Allerdings limitieren weiterhin die eingeschränkten Ressourcen der Hardwareplattform<br />
die Anzahl der realisierbaren Programme.<br />
Eine Möglichkeit, die maximal verfügbaren Systemressourcen zu erweitern ist es,<br />
mehrere gekoppelte FPGAs zu verwenden. Es ist abzusehen, dass die Kommunikation<br />
mit einem anderen Chip deutlich mehr Zeit erfordern wird, als innerhalb eines Chips.<br />
Die Auswirkungen einer solchen, verteilten Architektur auf den Traversierungsalgorithmus<br />
sollten untersucht werden.<br />
Desweiteren gilt es zu überprüfen, welche Möglichkeiten zukünftige dynamisch rekonfigurierbare<br />
Plattformen bieten. Neben einer höheren Zahl von Objekten sind insbesondere<br />
die Möglichkeiten zum Verschieben von logischen Blöcken interessant, weil<br />
für die Laufzeit der Traversierung die geometrische Position einer ComputingPage von<br />
Bedeutung ist. Wenn die Möglichkeit besteht, diese Position mit vertretbarem Zeitaufwand<br />
zu verändern, sollte untersucht werden, ob die <strong>Garbage</strong>-<strong>Collection</strong> davon profitieren<br />
kann.<br />
Die Möglichkeiten zur Laufzeitoptimierung der <strong>Garbage</strong>-<strong>Collection</strong> müssen weiter<br />
verfeinert werden. Hierfür ist die Wartezeit zwischen zwei GC-Läufen von besonderer<br />
Relevanz, weil sie sich beliebig festlegen lässt. Besonders interessant wäre eine, auf<br />
dem Erfolg des letzten GC-Zyklus basierende, dynamische Anpassung der Zeit zwischen<br />
zwei Wartezyklen, da Lösung nur wenig zusätzlichen Aufwand erfordert. Ihre
6.2. AUSBLICK 59<br />
Leistungsfähigkeit muss allerdings erst evaluiert werden.<br />
Andere, komplexere Algorithmen zur <strong>Garbage</strong>-<strong>Collection</strong>, wie etwa die Generational<br />
<strong>Collection</strong> erfordern einen deutlichen Mehraufwand bei der Verwaltung der Objekte.<br />
Es muss untersucht werden, ab welcher Komplexität der implementierten Programme<br />
dieser Mehraufwand gerechtfertigt ist und zu einer höheren Gesamtleistung führt.<br />
Schließlich sollte in Betracht gezogen werden, ob sich der entworfene Algorithmus<br />
auch bei anderen Problemstellungen einsetzen lässt. Insbesondere die effiziente Traversierung<br />
eines Graphen bietet sich für eine Vielzahl von Anwendungen an.
60 KAPITEL 6. ZUSAMMENFASSUNG
Literaturverzeichnis<br />
[1] B. Mayer, “Object-Oriented Software Construction, Second Edition”, Prentice<br />
Hall ISBN 0-13-629155-4 March 21, 2000.<br />
[2] A. Kühn, S. Huss, “Dynamically Reconfigurable Hardware for Object Oriented<br />
Processing” International Conference on Parallel Computing in Electrical<br />
Engineering, Dresden, Germany September 2004.<br />
[3] Xilinx, “Programmable Logic Data Book,” 2001.<br />
[4] Xilinx, “XAPP290 : Two Flows for Partial Reconfiguration : Module Based or<br />
Small Bit Manipulations,” Xilinx Application Notes, 2004<br />
[5] Xilinx, Inc., “ISE Foundation 6.3i”<br />
http://www.xilinx.com/products/design_resources/design_tool/<br />
[6] Alpha Data Parallel Systems Ltd., “ADC-PMC-64 User Manual”, Ver. 1.1, 2002.<br />
[7] Synplicity, Inc., “Synplify Pro 8.0”<br />
http://www.synplicity.com/products/synplifypro/index.html<br />
[8] B.W. Kernighan, D. Ritchie, “C Programming Language, Second Edition”,<br />
Prentice Hall ISBN 0131103628<br />
[9] P. Roy, S. Seshadri, A. Silberschatz, S. Sudarshan, S. Ashwin, “<strong>Garbage</strong> collection<br />
in object-oriented databases using transactional cyclic reference counting”<br />
The VLDB Journal, 7(3):179-193, 1998.<br />
[10] A. Diwan, D. Tarditi, E. Moss, “Memory subsystem performance of programs<br />
using copying garbage collection”, Proceedings of the 21st ACM SIGPLAN-<br />
SIGACT symposium on Principles of programming languages, February 1994<br />
[11] E. W. Dijkstra, L. Lamport, A. J. Martin, C. S. Scholten, E. F. M. Stevens, “Onthe-Fly<br />
<strong>Garbage</strong> <strong>Collection</strong>: An Exercise in Cooperation”, Communications of<br />
the ACM, 21(11):966-957, November 1978.
62 LITERATURVERZEICHNIS<br />
[12] L. Huelsbergen, P. Winterbottom, “Very Concurrent Mark-&-Sweep <strong>Garbage</strong><br />
<strong>Collection</strong> without Fine-Grain Synchronization”, ACM SIGPLAN Notices Volume<br />
34 Issue 3, Proceedings of the 1st international symposium on Memory<br />
management, October 1998<br />
[13] M. Ben-Ari, “Algorithms for On-the-fly <strong>Garbage</strong> <strong>Collection</strong>”, ACM Transactions<br />
on Programming Languages and Systems (TOPLAS), Volume 6 Issue 3, July<br />
1984<br />
[14] D. Doligez, G. Gonthier, “Portable, unobtrusive garbage collection for multiprocessor<br />
systems”, Proceedings of the 21st ACM SIGPLAN-SIGACT symposium<br />
on Principles of programming languages, February 1994<br />
[15] T. Domani, E. K. Kolodner, E. Lewis, E. E. Salant, K. Barabash, I. Lahan, Y.<br />
Levanoni, E. Petrank, I. Yanorer, “Implementing an on-the-fly garbage collector<br />
for Java”, ACM SIGPLAN Notices , Proceedings of the 2nd international<br />
symposium on Memory management, Volume 36 Issue 1, October 2000<br />
[16] D. Doligez, X. Leroy, “A concurrent, generational garbage collector for a multithreaded<br />
implementation of ML”, Proceedings of the 20th ACM SIGPLAN-<br />
SIGACT symposium on Principles of programming languages, March 1993<br />
[17] J. Seligmann, S. Grarup, “Incremental Mature <strong>Garbage</strong> <strong>Collection</strong> Using the<br />
Train Algorithm”, Proceedings of ECOOP’95, Ninth European Conference on<br />
Object-Oriented Programming, Lecture Notes in Computer Science, Vol. 952,<br />
pp. 235-252, 1995<br />
[18] H. Lieberman, C. Hewitt, “A Real-Time <strong>Garbage</strong> Collector Based on the Lifetime<br />
of Objects”, Communications of the ACM, 26(6):419-429, June 1983.<br />
[19] M. Hirzel, A. Diwan, M. Hertz, “Connectivity-based garbage collection”, ACM<br />
SIGPLAN Notices , Proceedings of the 18th annual ACM SIGPLAN conference<br />
on Object-oriented programing, systems, languages, and applications, Volume<br />
38 Issue 11, October 2003<br />
[20] T.H. Cormen, C.E. Leiserson, R.L. Rivest, “Introduction to Algorithms”, MIT<br />
Press, ISDB 0-262-53091-0<br />
[21] B. Zorn, D. Grunwald “Evaluating Models of Memory Allocation”, ACM Transactions<br />
on Modeling and Computer Simulation, 4(1):107-131, January 1994<br />
[22] S. Blackburn, P. Cheng, K.S. McKinley, “Myths and Realities: The Performance<br />
Impact of <strong>Garbage</strong> <strong>Collection</strong>” Proceedings of ACM SIGMETRICS/Performance,<br />
ACM 1-58113-873-3/04/0006, June 2004
LITERATURVERZEICHNIS 63<br />
[23] M. Hirzel, J. Henkel, A. Diwan, M. Hind “Understanding the Connectivity of<br />
Heap Objects” Proceedings of ISSM ’02, ACM 1-58113-539-4/02/0006, June<br />
2002<br />
[24] C. Ruggieri, T.P. Murtagh, “Lieftime Analysis of Dynamically Allogcated Objects”<br />
Proceedings of 15th Annual ACM SIGACT-SIGPLAN Symposium on Principles<br />
of Programming Languages, January 19989<br />
[25] D. Stefanovic, K.S. McKinley, J.E.B. Moss, “On Models for Object Lifetime<br />
Distribuntions” Proceedings of ISSM ’00, ACM 1-58113-263-8/00/10, October<br />
2000<br />
[26] S. Oaks, H. Wong, “Java Threads, Third Edition” O’Reilly, ISBN 0596007825<br />
[27] J. Gosling, B. Joy, G. Steele, G. Bracha, “Java(TM) Language Specification,<br />
Third Edition” Addison-Wesley Professional, ISBN: 0321246780