Simulation von Programmen für Netzwerk - Parallelrechner. Bachelorarbeit. Torben Wichers. Referent: Prof. Dr. Rainer Parchmann



Ähnliche Dokumente
Einführung in die C++ Programmierung für Ingenieure

1 Mathematische Grundlagen

Übersicht. Nebenläufige Programmierung. Praxis und Semantik. Einleitung. Sequentielle und nebenläufige Programmierung. Warum ist. interessant?

Grundlagen der Theoretischen Informatik, SoSe 2008

Erweiterung der Aufgabe. Die Notenberechnung soll nicht nur für einen Schüler, sondern für bis zu 35 Schüler gehen:

1 Vom Problem zum Programm

Zählen von Objekten einer bestimmten Klasse

5 DATEN Variablen. Variablen können beliebige Werte zugewiesen und im Gegensatz zu

Einführung in die Programmierung

Outlook. sysplus.ch outlook - mail-grundlagen Seite 1/8. Mail-Grundlagen. Posteingang

Datenbanken Kapitel 2

Arbeiten mit UMLed und Delphi

Einführung zum Arbeiten mit Microsoft Visual C Express Edition

Lineargleichungssysteme: Additions-/ Subtraktionsverfahren

Synchronisierung. Kommunikationstechnik, SS 08, Prof. Dr. Stefan Brunthaler 73

Wintersemester Maschinenbau und Kunststofftechnik. Informatik. Tobias Wolf Seite 1 von 18

2.5. VERBINDUNGSNETZWERKE GESTALTUNGSKRITERIEN DER NETZWERKE TOPOLOGIE ALS GRAPH. Vorlesung 5 TOPOLOGIE: DEFINITIONEN : Sei G = (V, E) ein Graph mit:

Primzahlen und RSA-Verschlüsselung

Systeme 1. Kapitel 6. Nebenläufigkeit und wechselseitiger Ausschluss

Konzepte der Informatik

10 Erweiterung und Portierung

Anleitung über den Umgang mit Schildern

Grundlagen verteilter Systeme

Arge Betriebsinformatik GmbH & Co.KG, CAP News 40, Februar CAP-News 40

Enigmail Konfiguration

Unterprogramme. Funktionen. Bedeutung von Funktionen in C++ Definition einer Funktion. Definition einer Prozedur

Monitore. Klicken bearbeiten

Graphic Coding. Klausur. 9. Februar Kurs A

WinVetpro im Betriebsmodus Laptop

Drucken aus der Anwendung

Übungskomplex Felder (1) Eindimensionale Felder Mehrdimensionale Felder

Zahlensysteme: Oktal- und Hexadezimalsystem

Einführung in die C-Programmierung

4 Aufzählungen und Listen erstellen

C++ Grundlagen. ++ bedeutet Erweiterung zum Ansi C Standard. Hier wird eine Funktion eingeleitet

M. Graefenhan Übungen zu C. Blatt 3. Musterlösung

Objektorientierte Programmierung

Synchronisations- Assistent

Java Kurs für Anfänger Einheit 4 Klassen und Objekte

Nutzung von GiS BasePac 8 im Netzwerk

Die Programmiersprache C

FuxMedia Programm im Netzwerk einrichten am Beispiel von Windows 7

CMS.R. Bedienungsanleitung. Modul Cron. Copyright CMS.R Revision 1

Inhalt. Allgemeine Einführung. Argumentationsvermögen. Räumliches Vorstellungsvermögen. Begabungen und Fähigkeiten messen

Handbuch. NAFI Online-Spezial. Kunden- / Datenverwaltung. 1. Auflage. (Stand: )

1 topologisches Sortieren

Hochschule Darmstadt Informatik-Praktikum (INF 1) WS 2015/2016 Wirtschaftsingenieur Bachelor 5. Aufgabe Datenstruktur, Dateieingabe und -ausgabe

Über Arrays und verkettete Listen Listen in Delphi

Dokumentation IBIS Monitor

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen

Modellierung und Programmierung 1

Outlook und Outlook Express

Einführung in die Programmierung (EPR)

Erwin Grüner

Das Handbuch zu Simond. Peter H. Grasch

... MathML XHTML RDF

Wir arbeiten mit Zufallszahlen

Matrix42. Use Case - Sicherung und Rücksicherung persönlicher Einstellungen über Personal Backup. Version September

4. BEZIEHUNGEN ZWISCHEN TABELLEN

Viele Bilder auf der FA-Homepage

Stundenerfassung Version 1.8 Anleitung Arbeiten mit Replikaten

A. Ersetzung einer veralteten Govello-ID ( Absenderadresse )

Speicher in der Cloud

Dokument Lob erstellen

ecaros2 Installer procar informatik AG 1 Stand: FS 09/2012 Eschenweg Weiterstadt

Wie halte ich Ordnung auf meiner Festplatte?

Domänenmodell: Fadenkommunikation und -synchronisation

Wir übertragen Daten mit Licht

Programmieren für Ingenieure Sommer Ein Rechner. Rechner sind überall. Gerät, das mittels programmierbarer Rechenvorschriften Daten verarbeitet.

Handbuch Fischertechnik-Einzelteiltabelle V3.7.3

Auswahlabfragen mit ACCESS

Computerarithmetik ( )

Internet online Update (Mozilla Firefox)

Objektorientiertes Programmieren mit Suse Linux

Einführung in die Programmierung

Das Typsystem von Scala. L. Piepmeyer: Funktionale Programmierung - Das Typsystem von Scala

Grundlagen der Informatik

Einführung in die Algebra

Zwischenablage (Bilder, Texte,...)

Kapitel 4 Die Datenbank Kuchenbestellung Seite 1

Programmierung in C. Grundlagen. Stefan Kallerhoff

Installation von Druckern auf dem ZOVAS-Notebook. 1. Der Drucker ist direkt mit dem Notebook verbunden

Kennen, können, beherrschen lernen was gebraucht wird

Aufklappelemente anlegen

Programmierkurs Java

E Mail Versand mit der Schild NRW Formularverwaltung

Java 7. Elmar Fuchs Grundlagen Programmierung. 1. Ausgabe, Dezember 2011 JAV7

In diesem Thema lernen wir die Grundlagen der Datenbanken kennen und werden diese lernen einzusetzen. Access. Die Grundlagen der Datenbanken.

Tipp III: Leiten Sie eine immer direkt anwendbare Formel her zur Berechnung der sogenannten "bedingten Wahrscheinlichkeit".

Daten-Synchronisation zwischen dem ZDV-Webmailer und Outlook ( ) Zentrum für Datenverarbeitung der Universität Tübingen

Rechenzentrum der Ruhr-Universität Bochum. Integration von egroupware an der RUB in Outlook 2010 mit Funambol

Rundung und Casting von Zahlen

Tutorium Informatik 1. Aufgabe 2: Formatierte Ein- und Ausgabe

Bereich METIS (Texte im Internet) Zählmarkenrecherche

GEVITAS Farben-Reaktionstest

Mediator 9 - Lernprogramm

Windows. Workshop Internet-Explorer: Arbeiten mit Favoriten, Teil 1

Beweisbar sichere Verschlüsselung

Grundbegriffe der Informatik

Transkript:

Simulation von Programmen für Netzwerk - Parallelrechner Bachelorarbeit im Rahmen des Bachelor Studiengangs Angewandte Informatik an der Universität Hannover von Torben Wichers Referent: Prof. Dr. Rainer Parchmann Angefertigt am Institut für Informationssysteme der Universität Hannover 10. Juli 2003

3 Zusammenfassung Aufgrund der steigenden Anforderung an Rechenleistung und Geschwindigkeit von Computern spielen Parallelrechner in der Zukunft eine immer größer werdende Rolle. Insbesondere Netzwerk-Parallelrechner sind wegen ihrer hohen Skalierbarkeit sehr beliebt, was sie zu primären Kandidaten für Teraflops-Rechner in diversen Projekten macht. Ein solches Projekt ist die Accelerated Strategic Computing Initiative (ASCI) des USamerikanischen Department of Energy. Das auf 10 Jahre (1996-2006) angelegte und mit einer Milliarde Dollar finanzierte Projekt dient der rechnergestützten Atombomben-Forschung der USA. Innerhalb der gesamten Zeit soll alle 18 Monate ein Rechner mit dreifacher Leistung installiert werden. Begonnen hat das Projekt mit dem ASCI-Red von Intel- Supercomputer Division mit einer Rechenleistung von einem Teraflop. Es wird erwartet, dass im Jahre 2005 eine Rechenleistung von 100 Teraflops benötigt wird ([Ung97] Seite 188f). Weitere Anwendungsgebiete sind z.b. Crashsimulationen in der Automobilindustrie oder Wetteranalysen bzw. -prognosen ([Mol01] Seite 4). Diese Arbeit beschäftigt sich mit der Simulation von Programmen für Netzwerk- Parallelrechner. In diesem Rahmen wurde eine komplette Entwicklungsumgebung geschaffen, die es dem Benutzer ermöglicht, die Techniken paralleler Algorithmen für Netzwerk-Parallelrechner mit Hilfe der neuen Programmiersprache PSL03NET zu erlernen und als Programm auf einem Einprozessorsystem zu simulieren und zu testen. Im Umfang der Entwicklungsumgebung sind ein Compiler, der das PSL03NET-Programm in einen Maschinencode übersetzt, ein Simulator bzw. Debugger, auf dem der Maschinencode ausgeführt wird und eine graphische Oberfläche enthalten. Die graphische Oberfläche dient als Alternative zur Kommandozeilen orientierten Entwicklung. Sie bietet eine benutzerfreundliche Möglichkeit PLS03NET-Programme zu schreiben, zu kompilieren und auszuführen. Während der Ausführung lassen sich sowohl über die Oberfläche als auch über den aus der Kommandozeile aufzurufenden Simulator der Zustand der Variablen, der Prozessoren und des Netzwerks überwachen.

4 Inhaltsverzeichnis 1 Einleitung 7 1.1 Aufgabenstellung 8 1.2 Zielsetzung 8 2 Grundlagen der parallelen DV 9 2.1 Klassifikation von Parallelrechnern 9 2.2 Kopplung der Prozessoren 10 2.3 Das PRAM-Modell 10 2.4 Netzwerk-Parallelrechner 12 2.4.1 Beispiele für statische Netzwerke 13 2.4.2 Kommunikation über ein Netzwerk 16 2.5 Die Programmiersprache PSL98 17 3 Die Programmiersprache PSL03NET 19 3.1 Entwurfsziele 19 3.2 Umsetzung 19 3.2.1 Änderungen an der Sprache PSL98 19 3.2.2 Änderungen am Compiler 22 3.2.3 Änderungen am Simulator 26 3.2.4 Die Graphische Entwicklungsumgebung 27 4 Formale Beschreibung von PSL03NET 29 4.1 Grundelemente der Sprache 29 4.1.1 Zeichensatz 29 4.1.2 Format von PSL03NET-Programmen 30 4.1.3 Kommentare 30 4.1.4 Operatoren 30 4.1.5 Schlüsselwörter 30 4.1.6 Namen 31 4.1.7 Konstanten 31 4.2 Bezeichnung von Variablen 32 4.3 Syntax der Sprache 33 4.3.1 Allgemeine Struktur eines Programms 33 4.3.2 Deklarationen und Definitionen 34 4.3.3 Anweisungen 39 4.3.4 Datentypen 43 4.3.5 Ausdrücke 45

Inhaltsverzeichnis 5 5 Der Simulator und Debugger 53 5.1 Das Rechnermodell 53 5.1.1 Synchronisieren eines SIMD-Multiprozessors 53 5.1.2 Synchronisieren eines MIMD-Multiprozessors 56 5.1.3 Das Prozessormodell 57 5.1.4 Das Speichermodell 58 5.1.5 Das Netzwerkmodell 58 5.2 Format der Eingabedatei 59 5.2.1 Debuginformationen 59 5.2.2 Programmcode 60 6 Benutzeranleitung 61 6.1 Der Compiler pslnetc 61 6.2 Der Simulator pslsim 61 6.3 Der Debugger psldb 62 6.3.1 Befehle des Debuggers 62 6.4 Die Graphische Entwicklungsumgebung psldev 64 6.4.1 Die graphische Oberfläche 65 6.4.2 Der Editor 66 6.4.3 Das Menüsystem 66 6.4.4 Die Toolbar 69 6.4.5 Der Infobereich 70 7 Ausblick 75 8 Anhang 77 8.1 Beispiele 77 8.2 Installationshinweise 84 8.2.1 Verzeichnisstruktur der Installations-CD 84 8.2.2 Vorkompilierte Versionen installieren 84 8.2.3 Quellcode kompilieren 85 8.3 Referenzen 85 8.3.1 Die Grammatik von PSL03NET 85 8.3.2 Der Befehlsatz des Simulators 91 8.3.3 Fest implementierte Netzwerke 96 9 Abbildungsverzeichnis 101 10 Literaturverzeichnis 103

7 1 Einleitung Diese Bachelorarbeit mit dem Thema Simulation von Programmen für Netzwerk- Parallelrechner ist im Rahmen des Bachelor Studiengangs Angewandte Informatik in Hannover entstanden. Sie beschreibt eine Programmierumgebung, mit der es möglich ist, Programme auf einem simulierten Netzwerk-Parallelrechner zu entwickeln und auszuführen. Diese Arbeit baut auf der Diplomarbeit Simulation von PRAM-Programmen von Christian Schweer aus dem Jahr 1998 auf [Sch98]. In dieser Arbeit wurde bereits eine Programmierumgebung für einen Parallelrechner entwickelt. Das durch die Umgebung simulierte Modell ist aber nur sehr theoretisch, daher soll diese Programmierumgebung auf das in der Praxis häufiger verwendete Modell eines Netzwerk-Parallelrechners erweitert werden. Die Arbeit lässt sich in drei große Abschnitte unterteilen. Im ersten Teil werden einige Grundlagen über Parallelrechner vereinbart. Im folgenden Teil, der dem Kapitel 3 entspricht, werden die an der zu erweiternden Programmierumgebung durchgeführten Änderungen vorgestellt und erklärt. Der dritte Teil, der die Kapitel 4 und 5 umfasst, beschreibt die gesamte Programmiersprache und den Simulator, die beide Bestandteil dieser Arbeit sind. Aufgrund der Tatsache, dass es sich bei dieser Arbeit um eine Erweiterung handelt, wurden einige Textpassagen aus der Diplomarbeit von Christian Schweer in diese Bachelorarbeit übernommen, um dem Leser eine vollständige Beschreibung der Programmierumgebung zur Verfügung zu stellen.

8 1.1 Aufgabenstellung Die Aufgabe dieser Arbeit ist es, eine Programmierumgebung zu schaffen, mit der man Programme für Netzwerk-Parallelrechner auf Einprozessorsystemen simulieren kann. Hierdurch soll die Möglichkeit geboten werden, die Techniken paralleler Algorithmen zu erlernen und als Programme auf einem Einprozessorsystem auszuführen und zu testen. Als Grundlage, auf der diese Arbeit aufbaut, steht die Programmiersprache PSL98 zur Verfügung. Diese Sprache wurde im Laufe einer Diplomarbeit von Christian Schweer 1998 entwickelt [Sch98]. Mit dieser ist es möglich, Programme für das PRAM Modell zu simulieren. Diese Sprache soll nun an die speziellen Eigenschaften und Anforderungen eines Netzwerk-Parallelrechners angepasst und erweitert werden. 1.2 Zielsetzung Hauptziel der Arbeit ist es, die einfache Art der Programmierung von PRAM-Modellen durch PSL98 auf die Programmierung von Netzwerk-Parallelrechnern zu übertragen. Die vorhandene Sprache PSL98 soll angepasst, sowie der vorhandene Simulator nur so weit erweitert werden, dass vorhandene PSL98 Programme weiterhin simuliert werden können. Dabei soll die Möglichkeit bestehen, aus einfachen Netzwerkmodellen auszuwählen, aber auch komplexere und speziellere zu definieren. Die Kommunikation zwischen den Prozessoren soll durch den Debugger überwacht werden, um dem Entwickler die Möglichkeit zu geben, den Ablauf des Programms inklusive Senden und Empfangen zu überwachen.

9 2 Grundlagen der parallelen DV In diesem Kapitel wird auf die Klassifizierung von Parallelrechnern sowie die Kopplung von Prozessoren eingegangen. Dabei werden die zwei folgenden Modelle vorgestellt: zum einen das PRAM-Modell als sehr theoretisches Modell und zum anderen das Modell des Netzwerk-Parallelrechners, das auch oft in der Praxis Verwendung findet. Speziell auf das Letztere wird in dieser Arbeit noch näher eingegangen. 2.1 Klassifikation von Parallelrechnern Eine sehr verbreitete Möglichkeit der Klassifizierung von Parallelrechnen ist die Flynnsche Klassifikation. Flynn teilt die gesamten Rechnerarchitekturen in vier Gruppen ein: SISD, SIMD, MISD und MIMD (siehe Abb. 2-1) ([Fly66], zitiert u. a. in [Brä93] Seite 4-5). Die Klassifizierung findet dabei anhand der Daten- und Programmströme statt. Einprozessorrechner SISD Single Instruction Single Data SIMD Single Instruction Multiple Data Vektorrechner Arrayrechner Pipelinerechner MISD Multiple Instruction Single Data MIMD Multiple Instruction Multiple Data Mehrprozessor rechner Verteiltes System Abbildung 2-1: Rechnerklassifikation nach Flynn Bei einem SISD Rechner handelt es sich um einen normalen Einprozessorrechner nach von Neumann und ist deshalb für diese Arbeit nicht interessant. Genau so wenig von Bedeutung für diese Arbeit ist die Klasse der MISD, bei der es sich im weitesten Sinne um Pipelinerechner handelt. Sie wird deshalb auch nicht weiter behandelt. Bei den beiden weiteren Klassen handelt es sich um Parallelrechner im klassischen Sinne mit mehreren parallelen Prozessoren, die miteinander verschaltet an einem gemeinsamen Problem arbeiten. In der Klasse der SIMD gibt es nur einen gemeinsamen Kontrollfluss für alle Prozessoren, d.h., dass alle Prozessoren denselben Befehl parallel ausführen und sich nur die Daten, die verarbeitet werden, unterscheiden. Alle Prozessoren laufen somit synchron. Dies führt zwangsläufig dazu, dass bei Kontrollanweisungen wie Abfragen oder Schleifen die Prozessoren, die die Bedingung nicht erfüllen, auf die anderen Prozessoren warten müssen. Das System kann somit nie seine maximale theoretische Leistung erreichen, da immer einige der Prozessoren auf andere warten.

10 Bei der Klasse der MIMD existiert für jeden Prozessor ein eigener Kontrollfluss und jeder Prozessor führt unabhängig sein eigenes Programm aus. Damit laufen die Prozessoren asynchron und es ist somit unbedingt vor jedem Datenaustausch zwischen den Prozessoren eine Synchronisation durch den Programmierer notwendig. Im Vergleich zu SIMD arbeitet dieses Modell effektiver, es erfordert allerdings durch die notwendige Synchronisation einen höheren Programmieraufwand. Aus diesen verschiedenen Klassen können durchaus auch sinnvolle Mischformen gebildet werden. Eine sehr interessante Mischung ist z.b. das SPMD Modell (same program multiple data), eine Kombination aus SIMD und MIMD. Wie der Name schon sagt, existiert auf Rechnern dieses Modells nur noch ein Programm, dieses wird aber nicht wie auf SIMD Rechnern von nur einem Kontrollfluss ausgeführt, sondern jeder Prozessor besitzt wie bei der Klasse der MIMD seinen eigenen. Dies wird genutzt, um die erheblichen Ineffizienzen bei der Ausführung von Kontrollanweisungen zu reduzieren, denn die Prozessoren müssen nur noch dann synchronisiert werden, wenn es im Programm zu einem Datenaustausch kommt. 2.2 Kopplung der Prozessoren Die Möglichkeiten zur Kopplung von Prozessoren lassen sich genau wie die Prozessoren selbst in einzelne Klassen einteilen. Dabei ist eine sinnvolle Unterteilung die nach speichergekoppelten und nachrichtengekoppelten Multiprozessorsystemen. Bei speichergekoppelten Multiprozessoren besitzen alle Prozessoren einen globalen Speicherbereich. Kommunikation und Synchronisation geschehen über die Variablen in diesem Speicher. Die Prozessoren werden dabei oft über einen Bus direkt mit dem Speicher verbunden. Bei den nachrichtengekoppelten Multiprozessoren geschieht der Datenaustausch über Nachrichten, die über ein Verbindungsnetz von Prozessor zu Prozessor gesendet werden. Die Synchronisation der Prozessoren findet dabei direkt durch diese Kommunikation statt. Ein globaler Speicher existiert in diesem System nicht. Weiterhin sind auch im Sinne der Kopplung von Prozessoren Kombinationen der beiden Klassen möglich. Diese Mischformen werden Distributed-shared-memory- Multiprozessoren genannt. ([Ung97] Seite 4) 2.3 Das PRAM-Modell Das PRAM-Modell ist ein sehr spezielles und theoretisches Modell, es spielt in der Praxis eine eher geringe Rolle, da es nur sehr schwer realisierbar ist. Beim Modell einer PRAM

Grundlagen der parallelen DV 11 (parallel random access machine) existieren für jeden Prozessor ein eigener lokaler Speicher, sowie ein gemeinsamer globaler Speicher, der alle Prozessoren miteinander verbindet (siehe Abb. 2-2). Im lokalen Speicher befinden sich das Programm sowie lokale Variablen, auf die nur der zugehörige Prozessor Zugriff hat. Im globalen Speicher können dagegen für jeden Prozessor zugängliche Daten abgelegt werden. Dieser Speicher kann somit zum Datenaustausch und zum Synchronisieren und Abstimmen der Prozessoren genutzt werden. Bei der PRAM handelt es sich somit um ein speichergekoppeltes Modell. Eine PRAM besitzt nur einen Kontrollfluss und muss somit der SIMD-Klasse zugeordnet werden. Lokaler Speicher Lokaler Speicher Lokaler Speicher CPU CPU CPU store Globaler Speicher load Abbildung 2-2: Schematischer Aufbau einer PRAM Damit es nicht zu Datenzugriffkonflikten kommt und jedem Prozessor in einem Instruktionsschritt die gleichen Daten zur Verfügung stehen, muss jeder Zugriff auf den globalen Speicher überwacht werden. Für die Restriktionen, die den Datenzugriff einschränken, um Konflikte zu vermeiden, gibt es vier verschiedene Klassifizierungen des PRAM Models. Diese Klassifizierungen unterscheiden sich darin, ob ein Lese- bzw. Schreibzugriff auf eine Speicherzelle während der parallelen Ausführung einer Instruktion von nur einem Prozessor oder mehreren Prozessoren konkurrierend durchgeführt werden darf. So darf auf einer EREW-PRAM (exclusive read, exclusive write) pro Instruktion immer nur ein Prozessor auf eine Speicherstelle des globalen Speichers schreiben bzw. lesen. Wenn mehr als ein Prozessor während einer Instruktion auf einer Speicherzelle lesen darf, nennt man das eine CREW-PRAM (concurrent read, exclusive write). Soll auch der Speicherzugriff auf einer Speicherzelle für mehr als einen Prozessor freigegeben werden, nennt man das CRCW-PRAM (concurrent read, concurrent write). Da bei diesem Modell

12 während einer Instruktion die Daten einer Speicherzelle mehrfach geändert werden können, muss für das Modell eine Regel existieren, die klärt, welche Daten zum Ende der Instruktion in der Speicherzelle übernommen werden. Möglich wäre z.b. ein common- CREW-PRAM, bei dem alle Prozessoren, die auf eine Speicherzelle schreiben, dieselben Daten schreiben müssen, damit es nicht zu einer Fehlermeldung kommt. Eine andere Idee wäre ein priority-crew-pram. In diesem Modell wird für jeden Prozessor eine Priorität vergeben und die Daten des Prozessors mit der höchsten Priorität werden nach der Instruktion in die Speicherzelle übernommen. Es sind noch viele andere Möglichkeiten denkbar. Als vierte Klasse der PRAM ist auch noch der ERCW-PRAM vorstellbar, bei dem nur ein Prozessor pro Instruktion lesen aber mehrere Prozessoren auf eine Speicherzelle schreiben dürfen. Dieses Modell wird allgemein nur sehr selten betrachtet. ([Par02] Kapitel 2.1) 2.4 Netzwerk-Parallelrechner Da das sehr theoretische Modell der PRAM sehr schwer zu realisieren ist, sind in der Praxis andere Modelle notwendig, um Parallelrechner zu bauen. Ein solches Modell ist der Netzwerk-Parallelrechner. Bei diesem wird auf den globalen Speicher verzichtet und durch ein Netzwerk, das die Prozessoren verbindet, ersetzt. Dabei besitzt jeder Prozessor weiterhin einen lokalen Speicher (siehe Abb. 2-3), es handelt sich damit um ein nachrichtengekoppeltes Modell. Netzwerke werden in SIMD- aber auch MIMD-Rechnern eingesetzt. Für die Realisierung der Verbindungsnetzwerke gibt es sehr verschiedene Lösungen, die sich im Allgemeinen in die Klassen der statischen sowie der dynamischen Netzwerke unterteilen lassen. Ein dynamisches Netzwerk besteht aus einem so genannten Schaltwerk, an das alle Einund Ausgänge angeschlossen sind. Über dieses können für jeden Datenaustausch dynamische Verbindungen zwischen Start- und Zielknoten hergestellt werden. Damit existiert niemals eine feste Verbindung zwischen zwei Knoten. Weiterhin ergeben sich daraus bei jedem Datenaustausch unterschiedliche Laufzeiten, die z.b. von der Auslastung des Netzwerks abhängen. Beim statischen Netzwerk dagegen handelt es sich um ein Netzwerk, das je zwei Knoten miteinander verbindet. Für den dadurch entstandenen Kanal sind somit bereits der Zugang zum Netzwerk und das jeweilige Ziel festgelegt. Damit hängt die Laufzeit nur von der Bauweise des Kanals ab, d.h. sie ist bei jedem Datenaustausch identisch. ([Ung97] Seite 27-29)

Grundlagen der parallelen DV 13 Lokaler Speicher Lokaler Speicher Lokaler Speicher CPU CPU CPU send receive Netzwerk Abbildung 2-3: Aufbau von Netzwerk-Parallelrechnern 2.4.1 Beispiele für statische Netzwerke In den Unterabschnitten dieses Kapitels folgen ein paar Beispiele für statische Netzwerke. Die Maßzahlen, die zur Beschreibung der Netzwerke herangezogen werden, sind die Anzahl der Prozessoren, der Verzweigungsgrad und der Durchmesser eines Netzwerks. Unter dem Verzweigungsgrad eines Netzwerks versteht man die maximale Anzahl an Kanälen pro Prozessoreinheit. Der Durchmesser beschreibt den maximalen Abstand zwischen zwei Prozessoreinheiten. 2.4.1.1 Feld und Ring Ein Prozessorfeld entspricht einer Anreihung von Prozessoren, in der jeder Prozessor mit seinem Folgeprozessor durch einen Kanal verbunden ist (siehe Abb. 2-4). In einem Prozessorring sind zusätzlich zu einem Prozessorfeld der erste und der letzte Prozessor durch einen Kanal verbunden. Abbildung 2-4: Feld und Ring mit fünf Prozessoren Der Verzweigungsgrad solcher Netzwerke ist 2. Der Durchmesser eines Prozessorfelds ist gleich der Anzahl an Prozessoren - 1. Bei einem Prozessorring entspricht der Durchmesser dagegen nur der Anzahl an Prozessoren durch 2. ([Par02] Kapitel 2.2.1)

14 2.4.1.2 Zweidimensionales Gitter und Torus Bei einem zweidimensionalen Gitter sind alle Prozessoren auf einer Gitterstruktur der Höhe m und der Breite n angeordnet (siehe Abb. 2-5). Es sind jeweils zwei benachbarte Prozessoren durch Kanäle verbunden. In einem Torus ist zusätzlich jeder Prozessor in der ersten Spalte mit seinem Partner in der letzten Spalte verbunden, gleiches gilt für die Prozessoren der ersten und letzten Zeile. Abbildung 2-5: Zweidimensionales Gitter und Torus (m=4, n=5) Die Anzahl der Prozessoren in einem solchen Netzwerk beträgt m*n bei einem Verzweigungsgrad von 4. Der Durchmesser beträgt bei einem Gitter m+n-2 und bei einem Torus (m+n)/2. ([Par02] Kapitel 2.2.2) 2.4.1.3 Hypercube Ein Hypercube der Dimension 0 ist ein einzelner Prozessor. Ein Hypercube der Dimension i+1 entsteht aus zwei Hypercubes der Dimension i, in dem die korrespondierenden Elemente miteinander verbunden sind. Damit ist, wie auch in Abbildung 2-6 zu sehen ist, z.b. ein Hypercube der Dimension 3 ein Würfel. Abbildung 2-6: Hypercubes der Dimensionen 0 bis 4

Grundlagen der parallelen DV 15 Der Verzweigungsgrad und der Durchmesser eines Hypercubes entsprechen seiner Dimension d. Die Anzahl an Prozessoren beträgt 2 d. ([Par02] Kapitel 2.2.3) 2.4.1.4 Der Binärbaum Ein Binärbaum-Netzwerk der Höhe 1 besteht aus einem einzelnen Prozessor. Einen Binärbaum der Höhe n+1 erhält man, indem man an alle Prozessoren mit dem Verzweigungsgrad 1 in einem Binärbaum der Höhe n zwei Folgeprozessoren anhängt und durch Kanäle mit diesen verbindet (siehe Abb. 2-7). Abbildung 2-7: Binärbäume der Höhen 1 bis 3 Die Anzahl der Prozessoren in einem solchen Netzwerk beträgt 2 n -1. Der Verzweigungsgrad ist 3 bei einem Durchmesser von 2*n-2. ([Brä93] Seite 49) 2.4.1.5 Der vollständige Graph Ein vollständiger Graph kann aus einer beliebigen Anzahl von Prozessoren bestehen. In solch einem Netzwerk wird jeder Prozessor mit jedem anderen durch einen Kanal verbunden. Abbildung 2-8: Vollständiger Graph mit fünf Prozessoren Bei einem vollständigen Graphen beträgt der Durchmesser 1 und der Verzweigungsgrad die Anzahl an Prozessoren - 1. ([Brä93] Seite 37)

16 2.4.2 Kommunikation über ein Netzwerk Da auf einem Netzwerk-Parallelrechner keine direkte Kommunikation über globale Variablen möglich ist, kann nur mit Hilfe von speziellen Mechanismen über ein Netzwerk kommuniziert werden. Bei der Kommunikation über ein Netzwerk werden Botschaften bzw. Nachrichten ausgetauscht und somit Daten an andere Prozessoren weitergeleitet. An die Stelle von Lesen und Schreiben von Variablen im globalen Speicher beim PRAM- Modell tritt bei Netzwerk-Parallelrechnern das Senden und Empfangen von Nachrichten. Eine Nachricht auf dem Netzwerk entsteht logisch gesehen dadurch, dass ein Prozessor eine Sendeoperation durchführt. Der Sender muss dabei Informationen über den Zielprozessor und die zu versendenden Daten bereitstellen, um eine Nachricht zu erzeugen. Eine Sendeoperation kann synchron und asynchron sein. Sie ist synchron, wenn der Sendebefehl den Prozessor bis zum Empfang der Nachricht beim Zielprozessor blockiert. Wenn die Zuständigkeit nach dem Senden sofort an den nächsten Befehl weitergegeben wird, nennt man die Sendeoperation asynchron. Wird die Nachricht nach dem Senden zuerst in einen Puffer geschrieben, aus dem die Daten vom Zielprozessor gelesen werden können, spricht man von einer gepufferten Sendeoperation. Falls dies nicht der Fall ist und der Prozessor sofort die Daten entgegen nimmt, handelt es sich um eine ungepufferte Sendeoperation. Das Empfangen der Nachricht beginnt, indem der Zielprozessor eine Empfangsoperation ausführt. Dabei muss der Prozessor die Information über den Quellprozessor und das Ziel der Daten zur Verfügung stellen. Das Empfangen der Nachricht führt dazu, dass die Daten der Nachricht sofort ausgelesen und gespeichert und im Normalfall sofort zerstört werden. Man nennt diese Art des Empfangens konsumierend oder zerstörend. Es ist auch möglich, dass das Empfangen konservierend abläuft. In diesem Fall würde die Nachricht nicht sofort zerstört werden und könnte mehrmals empfangen werden. Dementsprechend müsste es dann eine Möglichkeit geben, um diese Nachricht am Ende zu zerstören. Auch bei der Empfangsoperation gibt es die Möglichkeit, dass sie synchron oder asynchron abläuft. Ähnlich wie bei der Sendeoperation wartet eine synchrone Empfangsoperation, bis eine Nachricht den angegebenen Spezifikationen entsprechend eintrifft. Bei einer asynchronen Sendeoperation dagegen würde der Prozessor einen Fehler produzieren, falls zur Ausführung keine Nachricht vorliegt. Der gesamte Nachrichtenaustausch wird synchron genannt, wenn sowohl der Sende- als auch der Empfangsbefehl synchron sind. In diesem Fall können die Daten direkt und ungepuffert vom Quellcomputer zum Zielcomputer transferiert werden.

Grundlagen der parallelen DV 17 Wenn aber entweder der Sende- und/oder der Empfangsbefehl asynchron sind, bezeichnet man den gesamten Nachrichtenaustausch als asynchron. Im Normalfall ist bei einem asynchronen Nachrichtenaustausch der Sendebefehl asynchron. Da in diesem Fall die beiden Prozessoren nicht gleichzeitig für einander bereitstehen, muss über einen Puffer kommuniziert werden, damit die Nachricht zwischen dem Senden und dem Empfangen aufgefangen wird. Durch den logischen Ablauf zwischen Senden und Empfangen wirkt eine Kommunikation über ein Netzwerk aber in jedem Fall synchronisierend. Diese Eigenschaft kann für die Synchronisation eines MIMD-Multiprozessors genutzt werden, wobei mindestens eine der Aktionen in der Nachrichtenübertragung synchronisiert werden sollte. ([Ung97] Seite 74-76) 2.5 Die Programmiersprache PSL98 Die Programmiersprache PSL98 wurde in der Diplomarbeit Simulation von PRAM- Programmen von Christian Schweer 1998 entwickelt. Die Sprache ist konzipiert, um Programme für das PRAM-Modell zu schreiben. Ein Programm besteht in dieser Sprache immer genau aus einer Datei und wird durch den zugehörigen Compiler in einen Zwischencode übersetzt. Dieser Zwischencode kann dann auf der zugehörigen simulierten Stackmaschine, die dem PRAM-Modell im Großen und Ganzen entspricht, ausgeführt werden. Bei der Entwicklung der Sprache lag das Hauptaugenmerk darauf, eine möglichst leicht zu erlernende Sprache zu entwickeln, mit der es möglich ist, die grundlegenden Algorithmen und Methoden paralleler Algorithmen auf einer PRAM sehr schnell zu erlernen. Aus diesem Grund wurde eine bereits existierende sequentielle Programmiersprache um parallele Sprachkonstrukte erweitert. Die Programmiersprache, an die PSL98 angelehnt wurde, ist C. Allerdings beschränkt sich PSL98 auf einen sehr viel kleineren Sprachumfang. Um das Programmieren von Programmen für das PRAM Modell noch komfortabler zu gestalten, wurde in den Simulator, auf dem das Programm ausgeführt wird, ein Debugger integriert. Dieser Debugger ermöglicht es dem Programmierer, die Programmausführung detailliert zu überwachen, um somit Fehler schnell entdecken zu können. ([Sch98] Kapitel 3)

19 3 Die Programmiersprache PSL03NET In diesem Kapitel wird die Entwicklungsphase von PSL98 zu PSL03NET dargestellt. Dazu werden zuerst die Ziele für die neue Sprache dargelegt, um dann auf die notwendigen Änderungen einzugehen. 3.1 Entwurfsziele Ziel der neuen Programmiersprache ist es, eine Möglichkeit zu bieten, die Programmiertechniken von Algorithmen auf Netzwerk-Parallelrechnern möglichst einfach zu erlernen. Dabei geht es mehr um eine Möglichkeit die Theorie anzuwenden, als um das genaue Abbilden der Wirklichkeit. Um der Umsetzung dieses Ziels gerecht zu werden, ist es sinnvoll, die Kommunikation auf dem Netzwerk ohne Verzögerungszeiten zu ermöglichen. Damit besteht die Notwendigkeit eines statischen Netzwerks, da andere sich im Zugriff durch ein aufwendigeres Zeitverhalten unterscheiden (siehe Kapitel 2.4). Die Struktur des Netzwerks soll nicht fest definiert sein, sondern kann für jedes Programm erneut erstellt werden, um eine möglichst genaue Anpassung des Netzwerks an den Algorithmus zu gewährleisten. Zur Synchronisation der Programme soll im Vergleich zu PSL98 auch die Möglichkeit von asynchron laufenden Prozessoren (MIMD) geboten werden. Für diesen Fall soll das Netzwerk für die Synchronisation der Prozessoren genutzt werden können. Außerdem soll die Möglichkeit geschaffen werden, dass sowohl die Programme von PSL98 sowie Programme der neuen Sprache PSL03NET auf dem Simulator lauffähig sind. 3.2 Umsetzung Um die Anforderungen zu erfüllen, ist es notwendig, dass sowohl Änderungen an der Programmiersprache und damit Änderungen am Compiler aber auch Änderungen am Simulator durchgeführt werden. Diese Änderungen werden in diesem Kapitel beschrieben. 3.2.1 Änderungen an der Sprache PSL98 Die Strukturänderungen des zu simulierenden Modells erfordern einige Änderungen an der Programmiersprache PSL98. Da auf Netzwerk-Parallelrechnern kein globaler Speicher existiert, ist es nicht mehr möglich, globale Variablen zu definieren. An Stelle dessen ist eine Beschreibung des lokalen Netzwerks notwendig. Außerdem wird eine Möglichkeit zur Kommunikation zwischen den Prozessoren über das lokale Netzwerk geschaffen. Diese Kommunikation soll bei Wahl eines MIMD-Multiprozessors auch die Möglichkeit zur Synchronisation bieten.

20 Der erste konkrete Unterschied zwischen den beiden Sprachen PSL98 und PSL03NET ist der Wegfall des Speicherzugriffs auf den globalen Speicher. Daher ist die Wahl der Speicherzugriffsregelung nicht mehr notwendig, dafür wird an dieser Stelle die Auswahl der Multiprozessorklasse angeboten. Als Auswahl stehen SIMD- oder MIMD- Multiprozessoren zur Verfügung: PROGRAM test <mimd simd spmd MIMD SIMD SPMD>... Da durch die Existenz nur eines Programmstranges das Modell eines MIMD- Multiprozessors nicht vollständig erfüllt ist, kann alternativ das SPMD-Modell gewählt werden, weil diese Bezeichnung dem Modell ebenfalls gerecht wird. Als weitere Folge auf den Wegfall des globalen Speichers wurde der Klassifizierer shared als Attribut für die Definition von Variablen entfernt. Durch diese Änderung lassen sich nur noch lokale Variablen erzeugen. Um die Definition des lokalen Netzwerks zu ermöglichen, wurde ein neues Konstrukt eingeführt. Bei der Syntax wurde sehr viel Wert auf die Verständlichkeit und Übersichtlichkeit gelegt. Die Netzwerkdefinition kann sich an einer beliebigen Stelle im Bereich vor den Funktionen befinden und wird durch das Schlüsselwort netdef eingeleitet. Darauf folgen die genaue Beschreibung des Netzwerks und der Name: netdef { identifier construktor canal-list } netname; identifier Der identifier beschreibt die Namensgebung eines beliebigen Prozessors im Netzwerk. Er ermöglicht eine mehrdimensionale Beschreibung des Netzwerks: prozessor identified by {var1, var2, var3, }; Die Variablen, die in den Klammern definiert werden, haben alle den Typ Integer und sind für alle Berechnungen des Netzwerks zugänglich. Sie stellen ein Tupel zur Identifizierung des Prozessors dar. Wird kein identifier angegeben, so wird ein eindimensionales Netzwerk erzeugt, in dem jeder Prozessor nur über die Variable # identifiziert wird.

Die Programmiersprache PSL03NET 21 Ein zweidimensionales Gitter könnte z.b. so eingeleitet werden: prozessor identified by {#x, #y}; constructor Der constructor erzeugt die einzelnen Prozessoren und deren Identifizierungen. Dabei wird die Anzahl an unterschiedlichen Werten (0, 1,, max - 1) pro Dimension des angegebenen Identifizierungstupels spezifiziert. Es kann auf alle vorherigen Dimensionen Bezug genommen werden. new net(var1, var2, ) initialize {exp1, exp2, }; Die angegebenen Variablen sind für die Parametrisierung des Netzwerks gedacht. Auch diese Variablen sind für Netzwerk-Berechnungen nutzbar. Ein constructor ist nur bei mehrdimensionalen Netzwerken notwendig. Falls das Netzwerk eindimensional ist, kann er auch weggelassen werden und es existiert der Folgende standardmäßig. new net($) initialize ($); Ein sinnvoller constructor für das Beispiel des zweidimensionalen Gitters könnte lauten: new net(width, height) initialize {width, height}; canal-list In der canal-list können nun einzelne Verbindungskanäle definiert werden, die die einzelnen Prozessoren miteinander verbinden. Hierfür können beliebig viele Kanalkonstrukte aneinander gehängt werden. Es muss aber mindestens ein Kanal definiert werden: canalname(var1, ) condition connect to canalcode canaltyp; Die Variablen stehen für alle Berechnungen dieses Kanals zur Verfügung. Es müssen aber keine Variablen definiert werden und für diesen Fall dürfen auch die Klammern entfallen. Über die condition kann die Existenz des Kanals genau definiert werden. Erst wenn die condition erfüllt ist, wird der Folgeprozessor berechnet. Falls keine condition eingegeben wird, existiert der Kanal für

22 jeden Prozessor. Die Syntax entspricht der einer normalen if-abfrage ohne else-klausel. Im canalcode wird dann der Folgeprozessor berechnet. Die allgemeine Syntax entspricht einer durch Kommata getrennten Liste von Ausdrücken im geschweiften Klammern. Jeder Ausdruck muss einer Berechnung des Folgewertes einer Dimension des Identifizierungstupels aus den aktuellen Tupelwerten entsprechen. Über den canaltyp wird dann festgelegt, ob es sich um einen Sende-, einen Empfangskanal oder um beides handelt. Wenn der canaltyp nicht spezifiziert wird, handelt es sich um einen bidirektionalen Kanal. Eingeleitet wird der Kanaltyp über das Schlüsselwort for gefolgt von send und/oder receive getrennt durch Kommata. Für das gestellte Beispiel könnten folgende Kanäle sinnvoll sein: left if (#x > 0) connect to {#x 1, #y}; right if (#x < width) connect to {#x + 1, #y};... Gestartet werden der Parallelmodus und das definierte Netzwerk anschließend über das bereits vorhandene start Konstrukt aus PSL98, nur dass anstelle der Anzahl der Prozessoren die Parameter des Netzwerk-Konstruktors als Argumente übergeben werden. Die Kommunikation über das lokale Netzwerk geschieht über zwei neue Befehle, die dem Programmierer zur Verfügung stehen. Diese lauten: send(netname.canal(..), data); receive(netname.canal(..), lvalue); Die Synchronisationseigenschaften dieser Befehle werden vom Compiler verborgen, auf die genaue Arbeitsweise wird noch später eingegangen. 3.2.2 Änderungen am Compiler In diesem Kapitel wird darauf eingegangen, welche Arbeit vom neuen Compiler zur Erfüllung der neuen Sprache PSL03NET erledigt wird. Vor allem wird darauf eingegangen, wie die mehrdimensionalen Prozessornummern und Kommunikationskanäle realisiert sind. Außerdem wird erklärt, wie die Synchronisation mit Hilfe des Netzwerks funktioniert. Die komplette Umsetzung eines lokalen Netzwerks soll hier an einem Beispiel veranschaulicht werden. Dafür wird das schon im vorherigen Kapitel erwähnte Beispiel eines zweidimensionalen Gitters benutzt.

Die Programmiersprache PSL03NET 23 netdef { processor identified by {#x, #y}; new net(width, height) initialize {width, height}; left if(#x > 0) connect to {#x - 1, #y}; right if(#x < (width - 1)) connect to {#x + 1, #y}; up if(#y > 0) connect to {#x, #y - 1}; down if(#y < (height - 1)) connect to {#x, #y + 1}; } grid2d; Das erste Problem bei der Umsetzung ist die Existenz von mehrdimensionalen Prozessornummern in PSL03NET, da in PSL98 nur eindimensionale Prozessorfelder vorgesehen sind. Um einerseits das Debuggen und andererseits den Zwischencode möglichst einfach zu halten, wird die Arbeit zwischen Compiler und Simulator aufgeteilt. Dabei arbeitet der eigentliche Simulator immer noch auf eindimensionalen Prozessornummern. Es wird eine Datenstruktur erzeugt, die zur Übersetzung zwischen den Identifikationen benutzt werden kann. Der Programmcode, der diese Datenstruktur erzeugt, wird vom Compiler generiert. Außerdem werden dem Simulator die Variablen des Identifizierungstupels mitgeteilt und mit Hilfe der erzeugten Datenstruktur vom Simulator mit Inhalt gefüllt. Die Datenstruktur befindet sich im globalen Speicher der simulierten Stackmaschine und hat folgenden Aufbau: typedef struct { int count; //Anzahl Elemente in dieser Dimension union { netdata* nval; //nächste Dimension int ival; //zugehörige eindimensionale Prozessornummer } next[]; } netdata; Damit der Simulator mit Hilfe dieser Datenstruktur für jeden Prozessor die richtigen Werte in die Tupelvariablen füllen kann, erzeugt der Compiler weitere SICO-Befehle (siehe Anhang 8.3.2) zu Beginn des Programmcodes. Das erste Kommando macht dem Simulator die Datenstruktur bekannt, indem es ihm deren Speicheradresse mitteilt. Die darauf folgenden übergeben dem Compiler die Adressen der Tupelvariablen. Gefüllt wird die Datenstruktur von einer Unterfunktion, die als Argumente die Parameter des Konstruktors bekommt und die Anzahl an Prozessoren zurückgibt. Der Pseudocode für das gegebene Beispiel ist:

24 shared int width, height; shared netdata data; private int #x, #y; int net(int in_width, int in_height) { # = 0; netdata *data1; width = in_width; height = in_height; data.count = width; for (#x = 0; #x < data.count; #x++) { data1 = data.next[#x].nval = new netdata(); data1->count = height; for (#y = 0; #y < data1->count; #y ++) { } } } return #; data1->next[#y].ival = #++; Diese Funktion wird vor dem Start des Parallelmodus ausgeführt, dabei werden als Funktionsargumente die Argumente des Startbefehls von PSL03NET benutzt. Mit der Anzahl an Prozessoren, die die Funktion zurückgibt, wird danach der PSTART-Befehl der Stackmaschine ausgeführt und die eigentliche Parallelverarbeitung gestartet. Für die Abbildung der Netzwerkkanäle werden Unterfunktionen pro Kanal erzeugt, über die die Berechnung des Folgeprozessors stattfindet. Die Funktionen haben als Rückgabewert einen Integertyp, der den Folgeprozessor eindimensional identifiziert. Als Parameter erhalten die Funktionen genau die Parameter, die bei der Definition des Kanals angegeben wurden. Der Codeblock der Funktion beinhaltet im Allgemeinen die Gültigkeitsabfrage des Kanals als if-abfrage, die bei nicht Erfüllen die Funktion beendet und eine -1 zurückgibt. Für den Fall, dass es sich um einen gültigen Kanal handelt, wird zuerst das neue Identifizierungstupel berechnet, um dann mit Hilfe der Netzwerk- Datenstruktur und den errechneten Werten die neue Prozessornummer zu ermitteln. Für den ersten Kanal des gegebenen Beispiels ergäbe sich folgender Code: shared int width, height; shared netdata data;

Die Programmiersprache PSL03NET 25 private int #x, #y; int left(void) { } int netdata data1; if (#x > 0) { // Berechnung des Folgetupels stack int new_#x = #x 1; stack int new_#y = #y; // Berechnung der Folgeprozessornummer mit Hilfe // der Datenstruktur if (data.count > new_#x) { data1 = data.next[new_#x]; if (data1.count > new_#y) { return data1.next[new_#y]; } else { return -1; // Fehler ungültiger Kanal } } else { return -1; // Fehler ungültiger Kanal } } else { return -1; // Fehler ungültiger Kanal } Nachdem nun das Netzwerk komplett initialisiert wurde, muss noch geklärt werden, wie die Kommunikation auf diesem stattfindet. Dafür muss der Compiler die beiden neuen PSL03NET Befehle send und receive übersetzen. Um dieses zu erfüllen, werden für den Zwischencode drei neue Befehle vorgesehen: ASEND (asynchrones Senden), SRECV (synchrones Empfangen) und ARECV (asynchrones Empfangen). Jeder dieser Befehle erwartet eine Prozessornummer, um eine Kommunikation mit diesem Prozessor durchzuführen (auf die genaue Funktionsweise wird im nächsten Kapitel eingegangen). Da die Syntax von PSL03NET aber keine Prozessornummer als Parameter vorsieht, sondern stattdessen einen Verbindungskanal, erstellt der Compiler zuerst den Aufruf der Unterfunktion, die dem Verbindungskanal entspricht, um damit den Folgeprozessor zu berechnen. Daraufhin erzeugt der Prozessor den eigentlichen Sende- oder Empfangs- Befehl. Die genaue Auswahl der Befehle hängt von der gewählten Multiprozessor-Klasse und der damit verbundenen Synchronisationsart ab. Falls es sich um einen Multiprozessor der Klasse SIMD handelt, erzeugt der Prozessor die Befehle ASEND und ARECV, da hierdurch keine zusätzliche Synchronisation durchgeführt wird. Für den Fall, dass es sich

26 um einen MIMD-Multiprozessor handelt, ist eine Synchronisation jedoch notwendig. Deshalb werden die Befehle ASEND und SRECV benutzt, da hiermit das Empfangen der Daten und damit die Prozessoren synchronisiert werden. 3.2.3 Änderungen am Simulator Als wichtigstes Kriterium für alle Änderungen am Simulator gilt, dass der Simulator immer abwärtskompatibel bleibt, um sowohl PSL98- als auch PSL03NET-Programme ausführen zu können. Wie im vorherigen Kapitel bereits beschrieben, wird nicht die gesamte Simulation des Netzwerks vom Compiler übernommen. Der Simulator wurde um ein lokales Netzwerk erweitert, das es ermöglicht, von jedem zu jedem Prozessor Daten zu senden und zu empfangen. Um die Kommunikation über das Netzwerk zu ermöglichen, sind Sende- und Empfangsbefehle im Simulator notwendig. Der wichtigste Grund für diese Verfahrensweise ist, dem Benutzer des Simulators eine Möglichkeit zu bieten, die Kommunikation zu überwachen. Außerdem kann so durch einfaches Maskieren jedes beliebige Netzwerk simuliert werden. Die dadurch neu entstandenen Maschinenbefehle lauten ASEND und ARECV für die asynchrone sowie SRECV für die synchrone Kommunikation. Der Befehl ASEND steht für asynchrones Senden, d.h. dass er die zu übertragenden Daten vom Stack des Sendeprozessors in einen Puffer des Empfangsprozessors schreibt und daraufhin mit der Ausführung fortfährt. Ist der Puffer noch durch eine andere nicht abgeschlossene Kommunikation gefüllt, gibt der Befehl einen Fehler aus und beendet die Programmausführung. Der Befehl ARECV steht für asynchrones Empfangen, d.h. dass er die zu empfangenden Daten aus dem Puffer des Prozessors ausliest und die Daten auf den Stack des Zielprozessors verschiebt, um dann sofort die Programmausführung fortzusetzen. Findet der Prozessor beim Abfragen des Puffers nicht die korrekte Anzahl an Bytes, bzw. findet er gar keine Daten, beendet er die Programmausführung mit einem Fehler. Der Befehl SRECV steht für synchrones Empfangen, und unterscheidet sich nur leicht in der Arbeitsweise zu ARECV. Er erhält zusätzlich eine synchronisierende Wirkung, indem er auf die zu empfangenden Daten wartet und wenn notwendig, den Programmfluss des Prozessors stoppt, falls sich zur Ausführungszeit keine Daten im Empfangspuffer befinden. Wenn die Daten korrekt empfangen wurden, fährt er mit der normalen Programmausführung fort. Als Folge auf die synchronisierenden Möglichkeiten der Kommunikation ist es notwendig, eine Erkennung für Deadlocks zu integrieren, d.h. wenn sich ein Kreis von wartenden Prozessoren ergibt, der sich nicht wieder auflösen lässt. Diese überwacht ständig die Prozessoren und gibt im Falle eines Deadlocks einen Fehler aus und beendet die

Die Programmiersprache PSL03NET 27 Programmausführung. Zusätzlich wird eine genaue Beschreibung der wartenden Prozessoren ausgegeben. Um bereits vor einem Programmabbruch die Kommunikation überwachen zu können, ist ein neuer Befehl für den Debugger implementiert worden. Der neue Befehl lautet info network und gibt die Information über alle gerade laufenden Transaktionen zwischen den beiden aktiven Prozessoren aus (siehe Abbildung 3-1). Dabei werden jeweils die beiden beteiligten Prozessoren, sowie die Anzahl an Bytes dieser Übertragung ausgegeben. (psldb) info network pslsim: prozessor #0 send 1 byte(s) to prozessor #2. pslsim: prozessor #1 send 1 byte(s) to prozessor #3. pslsim: prozessor #2 send 1 byte(s) to prozessor #0. pslsim: prozessor #3 send 1 byte(s) to prozessor #1. (psldb) Abbildung 3-1: Beispielausgabe für den Befehl info network Außerdem wird bei der Statusausgabe der Prozessoren durch den Befehl info prozessors die Information ausgegeben, ob ein Prozessor und wenn auf welchen Prozessor er wartet. Eine weitere Änderung der Sprache, die Auswirkungen auf den Simulator hat, sind die mehrdimensionalen Prozessornummern. Für diese müssen für jeden Prozessor die Variablen des Identifizierungstupels gefüllt werden. Dafür muss dem Simulator die Speicheradresse der Tupelvariablen sowie die Adresse der Datenstruktur bekannt gegeben werden. Wie im vorherigen Kapitel beschrieben erzeugt der Compiler hierfür weitere SICO-Befehle am Anfang des Zwischencodes. Diese Befehle werden vom neuen Simulator als Speicheradressen interpretiert. Dabei definiert der erste neue Befehl die Adresse der Datenstruktur. Falls keine Datenstruktur existiert, wird als Adresse eine -1 übergeben. Alle folgenden Befehle werden zur Definition der Tupelvariablen benötigt. Mithilfe dieser Informationen füllt der Simulator nach dem Wechsel in den Parallelmodus die Variablen des Identifizierungstupels mit den Werten aus der Datenstruktur. 3.2.4 Die Graphische Entwicklungsumgebung Um das Programmieren von Algorithmen mit PSL98 und PSL03NET noch übersichtlicher zu gestalten, wurde eine Graphische Entwicklungsumgebung entwickelt, die eine Alternative zur Kommandozeilen orientierten Entwicklung bietet.

28 Sie soll es ermöglichen Programme zu schreiben, zu kompilieren und später auszuführen und zu debuggen. Dabei sollen gerade beim Debuggen die gleichen Möglichkeiten der Überwachung von Informationen wie beim textbasierten Debuggen geboten werden. Um die Unabhängigkeit vom Betriebssystem zu gewährleisten, wurde die Entwicklungsumgebung in Java programmiert. Es können wahlweise PSL98- oder PSL03NET-Programme geschrieben werden. Die Syntax des Programmcodes wird zur besseren Lesbarkeit und Erkennung der Struktur hervorgehoben, wie man es auch von anderen Programmiersprachen kennt. Das Programm kann nach den Änderungen direkt über das Menü kompiliert werden. Die erzeugten Warnungen bzw. Fehler werden in einem Statusfenster ausgegeben und können aus diesem Fenster direkt ausgewählt werden, um die entsprechende Zeile im Quellcode hervorzuheben. Dieses soll die Fehlerfindung erheblich erleichtern. Wenn ein Programm ohne Fehler kompiliert wurde, kann der Debugger gestartet und das Programm ausgeführt werden. Haltepunkte können direkt im internen Editor gesetzt und entfernt werden. Bei jeder Unterbrechung des Debuggers werden in entsprechenden Fenstern Informationen über den Zustand der Prozessoren, ihrer Stacks und die laufenden Kommunikationen ausgegeben. Zusätzlich können beliebige Variablen überwacht werden. Die Ausgabe erfolgt entweder durch eine Auswahl in einem speziellen Fenster oder durch das Markieren der Variable mit der Maus im Editorfenster. Die Ausgaben des eigentlichen Programms werden an ein eigenes Fenster weitergeleitet und somit von den restlichen Ausgaben des Debuggers getrennt. In diesem Fenster können auch die notwendigen Eingaben getätigt werden. Ein weiteres Fenster wird für Tracing Ausgaben bereitgehalten. Das Tracing lässt sich bei Bedarf im Menü ein- oder ausschalten. Zum Kompilieren und Debuggen werden von der Entwicklungsumgebung Prozesse mit dem textbasierten Compiler oder Debugger ausgeführt, deren Ausgaben von der Entwicklungsumgebung überwacht und interpretiert werden. Alle weiteren Beschreibungen können in der Benutzeranleitung zur Graphischen Entwicklungsumgebung in Kapitel 6.4 nachgelesen werden.

29 4 Formale Beschreibung von PSL03NET In diesem Kapitel wird ein kompletter Überblick über die Sprache PSL03NET gegeben. Dafür werden zuerst die Grundelemente der Sprache bzw. die lexikalische Analyse vorgestellt. Im Kapitel 4.3 wird dann die syntaktische Struktur mit Erläuterungen zur Semantik der Sprache PSL03NET dargestellt. 4.1 Grundelemente der Sprache Ein Programm wird bei der Verarbeitung durch einen Compiler zuerst einer lexikalischen Analyse unterzogen. Bei dieser Analyse werden die einzelnen Zeichen des Programmcodes zu einer Folge von Eingangssymbolen, den so genannten Tokens zusammengefasst. Der Programmtext wird sozusagen in die Grundelemente der Sprache PSL03NET zerlegt. Diese einzelnen Elemente können daraufhin in der lexikalischen Analyse wie in Kapitel 4.3 beschrieben ihrem Sinn nach untersucht werden. Die einzelnen Eingangsymbole lassen sich in folgende Klassen unterteilen: Namen, Schlüsselwörter, Konstanten, Operatoren und Zwischenräume. Als Zwischenräume werden Kommentare, Leerzeichen, Tabulatorzeichen und Zeilentrenner aufgefasst. Sie haben im Programm außer der Trennung der einzelnen Tokens voneinander keine weitere Bedeutung. Nachdem ein Teil der Eingabe zu einem Eingangssymbol zusammengefasst worden ist, wird als nächstes Eingangssymbol die längstmögliche Zeichenkette aufgefasst, die ein erlaubtes Eingangssymbol darstellt. 4.1.1 Zeichensatz Folgende Zeichen sind in PSL03NET-Programmen zulässig: Die alphanumerischen Zeichen: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 Das Leerzeichen, Tabulator und Zeilenendezeichen die folgenden Sonderzeichen: ( ) [ ] { } < > + - * / % ^ - _ ~ & =! # $ \,. ; ' "

30 4.1.2 Format von PSL03NET-Programmen PSL03NET-Programme müssen keine spezielle Zeilenstruktur vorweisen. Das Leerzeichen, Zeilenende und Tabulator werden außerhalb einer Zeichenkonstante und Zeichenfolge als Trennzeichen aufgefasst, durch die die einzelnen Grundelemente der Sprache getrennt werden. Folgen mehrere dieser Trennzeichen aufeinander, so werden diese wie ein logisches Trennzeichen angesehen. 4.1.3 Kommentare Um Kommentare einzufügen, gibt es zwei verschiedene Möglichkeiten. Die erste Möglichkeit ist, durch // die gesamten folgenden Zeichen bis zum Ende der Zeile als Kommentar zu markieren. Die zweite Möglichkeit ist, durch /* und */ geklammerte Texte (auch über mehrere Zeilen) als Kommentar zu markieren. Kommentare werden vom Compiler überlesen und nicht interpretiert. 4.1.4 Operatoren Mit Hilfe von Operatoren lassen sich Ausdrücke formulieren. PSL03NET behandelt folgende Zeichen/Zeichenketten als Operatoren: [ ] ( ) { }., = ; ++ -- * + - / % ~! << >> < > <= >= ==!= ^ & && *= /= %= += -= <<= >>= &= ^= = Die genaue Bedeutung der Operatoren wird im Abschnitt 4.3 genauer beschrieben. 4.1.5 Schlüsselwörter Die folgenden Ausdrücke sind reserviert und können nur mit ihren vordefinierten Bedeutungen, die jeweils im Abschnitt 4.3 erläutert werden, verwendet werden. # $ abs atof atoi by ceil char connect const do else exit exp fabs float floor for identified if initialize int log log10 log2 net netdef new NIL MIMD mimd pow printsystemdates private processor PROGRAM program read receive return send shared SIMD simd SPMD spmd start struct to typedef void while write

Formale Beschreibung von PSL03NET 31 4.1.6 Namen Mit Namen, die auch Bezeichner oder Identifier genannt werden, werden Variablen, Funktionen und Typen benannt. Ein Name muss mit einem Buchstaben beginnen, wobei der Unterstrich '_' zu den Buchstaben gezählt wird. Die restlichen Zeichen können Buchstaben oder Ziffern sein. Ein Name darf beliebig lang sein, die Länge wird lediglich durch den zur Verfügung stehenden dynamischen Speicher begrenzt, wobei alle Zeichen signifikant sind. 4.1.7 Konstanten 4.1.7.1 Ganzzahlige Konstanten Ganzzahlige Konstanten haben immer den Typ int mit dem Typattribut const. Sie bestehen aus einer ununterbrochenen Folge der Ziffern 0 bis 9, die normalerweise dezimal, also zur Basis 10, interpretiert wird. Beginnt die Konstante mit der Ziffer 0, wird sie oktal, also zur Basis 8, interpretiert. In diesem Fall sind die Ziffern 8 und 9 nicht zugelassen. Beginnt die Zeichenfolge mit den Zeichen 0x oder 0X wird sie zur Basis 16, also hexadezimal, interpretiert. Zu den Ziffern zählen dann zusätzlich die Buchstaben a bis f bzw. A bis F mit den Werten 10 bis 15. Zusätzlich darf einer Konstante ein + oder vorausgehen, um positive bzw. negative Konstanten zu definieren. Syntaktisch gehören die Zeichen aber nicht zu den Konstanten, sondern werden als unäre Operatoren behandelt. 4.1.7.2 Gleitpunktkonstanten Eine Gleitpunktkonstante hat immer den Typ float mit dem Typattribut const. Sie besteht aus einem ganzzahligen Teil, einem Dezimalpunkt, einem Dezimalbruch, dem Zeichen e oder E und einem ganzzahligen Exponenten mit optionalem Vorzeichen. Entweder der ganzzahlige Teil oder der Dezimalbruch darf fehlen. Analog darf entweder der Dezimalpunkt oder der Exponent beginnend mit e bzw. E fehlen. Wie bei ganzzahligen Konstanten dürfen auch Gleitpunktkonstanten Vorzeichen vorausgehen, die aber wiederum syntaktisch nicht zu den Konstanten gehören. 4.1.7.3 Zeichenkonstanten Eine Zeichenkonstante ist ein einzelnes Zeichen, eingeschlossen in einfache Anführungszeichen, also z.b. 'a'. Eine Zeichenkonstante hat immer den Typ char mit dem Attribut const. Der Wert einer Zeichenkonstante ist der numerische Wert des Zeichens im Zeichensatz der jeweiligen Maschine. In den meisten Fällen, insbesondere unter Linux, Solaris und Windows, ist dies der ASCII-Zeichensatz.