fortran - Department of Information Systems
Transcription
fortran - Department of Information Systems
Westfälische Wilhelms-Universität Münster Ausarbeitung FORTRAN im Rahmen des Seminars „Programmiersprachen“ im Fachbereich Informatik am Lehrstuhl für Praktische Informatik Robert Moeck Themensteller: Prof. Dr. Herbert Kuchen Betreuer: Prof. Dr. Herbert Kuchen Institut für Wirtschaftsinformatik Praktische Informatik in der Wirtschaft Inhaltsverzeichnis 1 Einführung ................................................................................................................. 1 2 Historische Entwicklung............................................................................................ 2 3 Ausgewählte Features von Fortran ............................................................................ 4 3.1 Grundlegende Struktur eines Fortran-Programms............................................ 4 3.2 Typkonzept ....................................................................................................... 6 3.3 Selbstdefinierte Ausdrücke ............................................................................... 7 3.4 Zeiger ................................................................................................................ 7 3.5 Felder ................................................................................................................ 8 Speicherverwaltung und dynamische Kontrolle ........................................................ 9 3.6 3.6.1 3.6.2 3.6.3 3.7 Zuordnungen................................................................................................... 10 Zuordnung über Namen .............................................................................. 10 Zuordnung über Zeiger ............................................................................... 11 Zuordnung über den Speicher..................................................................... 11 Ein- und Ausgabe............................................................................................ 12 Formatierung ........................................................................................................... 13 4 Anwendungsgebiete von Fortran-Systemen ............................................................ 15 Parallele Programmierung...................................................................................... 15 5 Quick Sort................................................................................................................ 17 6 Zusammenfassung und Ausblick............................................................................. 19 A Fortran 95-Quellcode für Quick Sort (rekursiv) ...................................................... 20 Literaturverzeichnis ........................................................................................................ 22 II 1 Einführung Die vorliegende Arbeit stellt die problemorientierte Programmiersprache Fortran vor. Als erste Hochsprache blickt Fortran auf eine Geschichte von rund 50 Jahren zurück und ist während dieser Zeit vielfach überarbeitet worden. Die Pioniere von Fortran haben zwar nicht die Idee erfunden, Programme in einer Hochsprache zu schreiben und den Quellcode zu kompilieren, aber sie haben die erste erfolgreiche Hochsprache entwickelt. Einen kurzen Überblick über die historische Entwicklung von Fortran gibt Kapitel 2. Darauf folgend werden in Kapitel 3 ausgewählte Eigenschaften und Spezifikationen der Sprache näher beleuchtet. Dabei wird weniger auf allgemeine Eigenschaften, wie z.B. Datentypen oder Konstrukte, über die nahezu alle Programmiersprachen verfügen, eingegangen. Vielmehr werden die Besonderheiten der Sprache Fortran hervorgehoben und erläutert. Im 4. Abschnitt werden die typischen Anwendungsgebiete von Fortran-Programmen dargestellt und Ansätze zur parallelen Programmierung beleuchtet. Anschließend wird die im Rahmen dieses Seminars programmierte Beispielanwendung Quick Sort vor- und der Implementierung in Java gegenübergestellt. Die Ausarbeitung wird mit einer kurzen Zusammenfassung und einem Ausblick in Kapitel 6 abgeschlossen. Kapitel 2: Historische Entwicklung 2 Historische Entwicklung FORTRAN I – Mitte der 1950er-Jahre verfügten die Rechner über äußerst kleine Hauptspeicher in der Größenordnung von 15 KB, waren langsam und hatten, wenn überhaupt, sehr primitive Betriebssysteme. Assembler-Programmierung war gängige Praxis. Die geistigen Väter von Fortran (Fortran = FORmula TRANslation), ein IBMTeam unter der Leitung von John W. Backus, entwickelten 1957 nach dreijähriger Arbeit den FORTRAN-I-Compiler. Besonders im wissenschaftlichen und militärischen Bereich verbreitete sich die neue Sprache schnell – die Vorteile lagen auf der Hand: Anwendungen, die zuvor Wochen für ihre Berechnungen brauchten, ließen sich binnen weniger Stunden abarbeiten. Die Anforderungen an den Programmierer waren wesentlich geringer als es bei der Assembler-Programmierung der Fall war. Und die Programme wurden portabel! FORTRAN 66 – Die Entwicklung ging (über die Zwischenversionen FORTRAN II, III und IV) weiter, die Sprache erhielt weitere Features. Seit 1962 beschäftigte sich die ASA (American Standards Association; der Vorläufer des ANSI, American National Standards Institute) mit der Entwicklung eines Standards für die Fortran, was 1966 mit der gleichnamigen Version als erstem Hochsprachen-Standard abgeschlossen wurde. FORTRAN 77 – Ein weiterer Meilenstein wurde im Jahre 1977 fertig gestellt und 1978 veröffentlicht. Zunächst ein amerikanischer ANSI-Standard, wurde FORTRAN 77 kurz darauf auch von der ISO (International Standards Organization) zum Standard erhoben – dieser galt somit weltweit. In dieser Neuauflage wurden viele wichtige Komponenten, wie z.B. Block-IF-Strukturen (IF – THEN – ELSE – ENDIF), der Pre-Test von DOSchleifen (vorher musste man einen Umweg über Sprungmarken nahmen, denn die Schleife wurde nur ein einziges Mal ausgeführt) und der CHARACTER-Datentyp hinzugefügt. Fortran 90 und 95 – Dass zwischen FORTRAN 77 und seinem Nachfolger über zehn Jahre lagen, begünstigte, dass andere Programmiersprachen, wie C oder C++, sich in den von Fortran dominierten Anwendungsgebieten – Naturwissenschaften und Ingenieurwesen – wachsender Beliebtheit erfreuten. Nichtsdestoweniger hat diese späte Überarbeitung des FORTRAN 77-Standards eine Menge mächtiger Erweiterungen eingeführt. Als wichtigste Neuerungen seien erwähnt: Spaltenunabhängige Codierung (in allen vorherigen Versionen mussten diesbezüglich strenge Konventionen 2 Kapitel 2: Historische Entwicklung eingehalten werden), weitere moderne Kontroll-Strukturen (CASE und DO WHILE), abgeleitete bzw. abstrakte Datentypen, Operator-Überladung (die Erweiterung bzw. ReDefinition der Funktionalität von Operatoren), dynamische Speicherverwaltung und Modularisierung. Genauer wird auf die Spezifikationen in Kapitel 3 eingegangen. Mit Fortran 90 wurde im Prinzip der heutige Stand der Technik erreicht – die aktuelle Version, Fortran 95, führte weniger Neues ein, als dass sie die Fehler und Inkonsistenzen von Fortran 90 korrigierte. Mit den in Fortran 90 implementierten Features enthielt die Sprache die wichtigsten auch heute noch gebräuchlichen Konstrukte und wurde wieder konkurrenzfähig. Hierbei spielt die Abwärtskompatibilität eine besondere Rolle: Fortran 90 enthält den vollen Funktionsumfang der Vorgängerversion, so dass auch die modernen Compiler den Code von vor über 20 Jahren verarbeiten können. 3 Kapitel 3: Ausgewählte Features von Fortran 3 Ausgewählte Features von Fortran Fortran ist eine imperative Programmiersprache. Ein imperatives Programm besteht aus Anweisungen (Instruktion, Befehl, statement). Es gibt Variablen, deren Wert sich durch Zuweisungen (assignments) ändern kann. Imperative Sprachen basieren i. a. auf dem Von-Neumann-Rechnermodell, d.h. Variablen bezeichnen Speicherplätze, Befehle richten sich an die CPU. Der Quellcode von Fortran-Programmen muss durch einen Compiler in semantisch äquivalenten Maschinencode übersetzt werden. 3.1 Grundlegende Struktur eines Fortran-Programms Ein Hauptprogramm wird durch die Anweisung PROGRAM eingeleitet und durch END PROGRAM beendet. Dazwischen befinden sich weitere Anweisungen, Funktions- oder Subroutinendefinitionen, oder Modul-Spezifikationen. Ein einfaches Beispiel: PROGRAM hallo_welt CALL hallo END PROGRAM hallo_welt !Hauptprogramm !Aufruf einer Subroutine !Ende des Hauptprogramms SUBROUTINE hallo PRINT *, ‘Hallo Welt!’ END SUBROUTINE hallo !Definition der Subroutine !Ausgabe von Hallo Welt! !Ende der Subroutine Programmeinheiten sind Hauptprogramm, externe Unterprogramme (Funktionen oder Subroutinen), block data-Unterprogramme und Module. Mit dem Hauptprogramm startet die Ausführung eines Programms. Das Hauptprogramm darf (ebenso wie externe Unterprogramme und Modul-Unterprogramme) hinter der Anweisung CONTAINS interne Unterprogramme enthalten. Durch die geschachtelten Aufrufe von (Unter-)Programmen entsteht eine hierarchische Struktur von Programmeinheiten. Eine Baumstruktur ist aber nicht überall streng eingehalten, weil ein Unterprogramm von vielen Seiten aufgerufen werden kann, und weil es Rekursion gibt. Funktionen und Subroutinen können extern als eigenständige Programmeinheiten, oder intern als Teile anderer Programmeinheiten definiert sein. Funktionen werden im aufrufenden Programm in Ausdrücken benutzt. Subroutinen werden mit CALL aufgerufen. Die Rekursion wird in Fortran unterstützt. Entsprechende Funktionen oder Subroutinen müssen mit dem Schlüsselwort RECURSIVE gekennzeichnet werden. Eine 4 Kapitel 3: Ausgewählte Features von Fortran Blockdatenprogrammeinheit ist eine nichtausführbare Programmeinheit, mit deren Hilfe Variablen benannter gemeinsamer Speicherbereiche initialisiert werden können. Ein Modul (seit Fortran 90) ist eine nichtausführbare Programmeinheit, die beliebige Typvereinbarungen, Spezifikationen, Schnittstellendefinitionen enthalten Modulkomponenten definiert darf. werden, Unterprogramme Es können wobei die sichtbare sichtbaren und/oder und von private anderen Programmeinheiten genutzt werden können [Ge91]. Beispiel: MODULE testmodul PUBLIC :: hochdrei CONTAINS SUBROUTINE hochdrei(zahl) INTEGER :: ergebnis ergebnis = zahl * zahl * zahl END SUBROUTINE hochdrei END MODULE testmodul PROGRAM haupt USE testmodul CALL hochdrei(5) END PROGRAM haupt !Modul mit !explizit sichtbar !deklarierter !Subroutine, die !ergebnis als Rück!gabewert hat. !Hauptprogramm, das das “Testmodul” !inkl. öffentlicher Subroutine !”hochdrei” nutzt und !5 hoch 3 ausrechnet. Komplexe Probleme lassen sich mit Hilfe von Unterprogrammen in Teilprobleme aufspalten. Jedes Teilproblem wird dabei in ein überschaubares Unterprogramm ausgelagert. Das ist für die Menschen, die das Gesamtprogramm entwickeln und warten, leichter zu überblicken und auf mehrere Mitarbeiter zu verteilen als ein ungegliedertes Großprogramm. Darüber hinaus können Compiler kleinere Einheiten oft besser optimieren als große. Sie setzen Register und andere Schnellspeicher geschickter ein. Allerdings kostet der Aufruf von Unterprogrammen auch etwas Zeit, so dass die Unterprogramme nicht zu wenig beinhalten sollten. Hinsichtlich der Wiederverwendbarkeit von Programmcode spielt die Modularisierung einer komplexen Anwendung eine große Rolle: Unterprogramme können in UnterprogrammBibliotheken abgelegt und von mehreren Programmen aus benutzt werden (z.B. mathematische und statistische Verfahren, Algorithmen zum Suchen und Sortieren, Graphik- und GUI-Programmpakete). Auch können Debugging-Strategien leichter genutzt werden, denn die Unterprogramme können einzeln auf Korrektheit untersucht werden. Natürlich führt die Benutzung von Unterprogrammen zu einer Reduzierung des gesamten Quellprogramms und auch des entstehenden Maschinenprogrammcodes, wenn die Unterprogramme mehrfach aufgerufen werden [Gr99]. 5 Kapitel 3: Ausgewählte Features von Fortran 3.2 Typkonzept Fortran benutzt eine statische Typbindung, d.h. der Quellcode legt die Datentypen der existierenden Größen fest. Damit wird die Typbindung und -prüfung zur Übersetzungszeit vorgenommen. Fortran ist nicht streng typisiert. In Fortran müssen nicht alle Variabeln deklariert werden, aber es ist sehr zu empfehlen. Die grundsätzliche Typkonvention ist: Wird ein Name verwendet, der nicht explizit deklariert ist, nimmt der Compiler entsprechend den Anfangsbuchstaben implizit einen der Grundtypen an, und zwar bei i, j, k, l, m, n per Voreinstellung den Typ integer, sonst real [Gr99]. Mit der IMPLICIT-Anweisung können zu diesem Zweck Buchstaben(bereiche) bestimmt werden, so dass Variablen, die mit entsprechenden Buchstaben beginnen, automatisch vom festgelegten Datentyp sind. Zwar erspart sich der Programmierer damit die explizite Deklaration, also einige Schreibarbeit, aber damit werden Schreibfehler in Variablennamen evtl. erst in der Testphase erkannt. Beispiel: IMPLICIT TYPE(student) (s), TYPE(dozent) (d) !Alle Variablen, die mit S beginnen, sind vom Typ student, !Alle Variablen, die mit d beginnen, sind vom Typ dozent. Mit der Anweisung IMPLICIT NONE wird die Typkonvention außer Kraft gesetzt, d.h. für jede Variable ist eine explizite Typvereinbarung erforderlich. Zusätzlich zu den vordefinierten Datentypen können bei Bedarf weitere Typen in Fortran „abgeleitet“ werden, d.h. mit Hilfe der existierenden Typen können neue Datentypen definiert werden. Ein solcher selbstdefinierter Datentyp hat mindestens eine Komponente, wobei jede Komponente wiederum einen vordefinierten oder selbstdefinierten Datentyp spezifiziert. Beispiel: TYPE wohnort CHARACTER (LEN=20) :: strasse INTEGER :: hausnr INTEGER :: plz CHARACTER (LEN=20) :: stadt END TYPE wohnort !Datentyp wohnort, der !ausschließlich aus !vordefinierten Typen !besteht. TYPE student CHARACTER (LEN=20) :: name INTEGER :: matr_nr TYPE (wohnort) :: adresse END TYPE student !Datentyp student, der !eine Strukturkomponente !vom Typ wohnort hat !bitte wenden… 6 Kapitel 3: Ausgewählte Features von Fortran TYPE (student), DIMENSION(150) :: ein_student !Ein Feld mit 150 Studenten PRINT *, ein_student(42)%matr_nr !Ausgabe der Matr.-Nr. von Student Nr. 42 Speicherfolge. Normalerweise ist durch die Reihenfolge der Typkomponenten keine Speicherfolge gegeben. Wenn die Typdefinition jedoch eine SEQUENCE-Anweisung enthält, dann spezifiziert die Reihenfolge der Typkomponenten eine Speicherfolge für Größen dieses Typs. Weiterhin wird durch SEQUENCE bewirkt, dass Größen dieses Typs mit bestimmten Einschränkungen in COMMON- und EQUIVALENCE-Anweisungen (Kap. 3.7.3) spezifiziert werden dürfen [Ge91]. Sichtbarkeit. Eine Typ- oder Komponentendefinition ist privat, wenn sie grundsätzlich nur innerhalb des Moduls zugänglich ist, das die Typdefinition enthält (PRIVATEAttribut). Eine sichtbare Typ- oder Komponentendefinition (PUBLIC-Attribut) kann mit Hilfe von USE auch außerhalb ihres Moduls zugänglich und nutzbar gemacht werden. 3.3 Selbstdefinierte Ausdrücke Ein selbstdefinierter Ausdruck besteht aus Operanden selbstdefinierten und/oder vordefinierten Typs und selbstdefinierten und/oder erweiterten vordefinierten Operatoren [Ge91]. Überladung. Die Funktionalität sowohl vordefinierter, als auch selbstdefinierter Operatoren kann durch die Definition von mehr als einer Operatorfunktion erweitert werden. Man spricht von Überladung. Wichtig ist dann die Eindeutigkeit der spezifizierten Operatorfunktionen, d.h. je zwei Operatorfunktionen müssen jeweils mindestens einen Formalparameter gleicher Position haben, die sich hinsichtlich Typ, Typparameter oder Rang unterscheiden. Bei Ausführung eines überladenen Operators bestimmen die Eigenschaften der Operanden, welche Operatorfunktion (implizit) aufgerufen wird [Ge91]. 3.4 Zeiger „Zeiger“ ist in Fortran kein spezieller Datentyp, sondern ein Attribut, das für Variablen oder selbstdefinierte Funktionen beliebigen Datentyps spezifiziert werden kann. Eine Variable oder eine Funktion mit POINTER-Attribut ist ein Zeiger. Dieser belegt eine unspezifische Speichereinheit. Ein Zeiger kann erst dann definiert und benutzt werden, 7 Kapitel 3: Ausgewählte Features von Fortran wenn er einem Ziel zugeordnet (mittels ALLOCATE) ist – erst dann kann man ihn wie eine „normale“ Variable behandeln, d.h. jeder Zugriff auf den Zeiger entspricht einem Zugriff auf das zugeordnete Ziel. Der Wert eines Zeigers ist also ein Verweis auf eine andere Datengröße desselben Typs wie der Zeiger, d.h. ein Zeiger ist typgebunden. Es gibt keine Zeigerkonstante, keinen NIL-Zeiger und auch kein Zeigerfeld, dessen Elemente Zeiger sind. Adressarithmetik ist in Fortran nicht möglich. Das Dereferenzieren erfolgt automatisch [Ge91]. Jedes Ziel, auf das ein Zeiger weisen soll, muss ein TARGET-Attribut haben, außer bei dem Ziel handelt es sich selbst um einen Zeiger. Dies dient lediglich der Verbesserung der Laufzeit-Effizienz des Fortran-Programms. Der Zuordnungsstatus eines Zeigers ist entweder „undefiniert“ (z.B. zu Beginn der Ausführung eines Programms), „zugeordnet“ oder „nicht zugeordnet“. Mittels ALLOCATE wird ein Zeiger einem (anderen) Ziel zugeordnet. Durch NULLIFY oder DEALLOCATE wird die Zuordnung aufgehoben. Der Zuordnungsstatus eines Zeigers darf bei Ausführung eines Unterprogramms geändert werden. Ist die Ausführung des Unterprogramms beendet, bleibt der Zuordnungsstatus bestehen (außer das Ziel wird beim Rücksprung in das aufrufende Programm undefiniert). 3.5 Felder Ein Feld (array) ist in Fortran eine regelmäßige Anordnung von skalaren Datenelementen mit gleichem Typ und Typparameter in Zeilen, Spalten, Ebenen, Kuben (Quadern) usw. (Vektoren, Matrizen, ...). Das einzelne Element ist durch ein Index-Tupel bestimmt. Ein Feld darf bis zu 7 Dimensionen haben, jede Dimension mit 0 oder mehr Elementen. Aufgrund der Feldspezifikation wird neben dem Rang des Feldes (= Anzahl der Dimensionen) auch dessen Gestalt bestimmt, wobei die folgenden drei Arten unterschieden werden: − Felder mit expliziter Gestalt: Die Index-Grenzen werden für jede Dimension explizit festgelegt, d.h. die Obergrenze muss, die Untergrenze kann für jede Dimension bestimmt werden (falls die Untergrenze fehlt, wird sie gleich 1 gesetzt). 8 Kapitel 3: Ausgewählte Features von Fortran − Felder mit übernommener Gestalt: Die Gestalt wird vom zugeordneten Aktualparameterfeld übernommen. Die Größe eines solchen Feldes ist gleich der Größe der entsprechenden Dimension des zugeordneten Aktualparameterfeldes. − Felder mit übernommener Größe: Die Größe eines solchen Feldes wird durch die Größe des zugeordneten Aktualparameterfeldes bestimmt. Im Gegensatz zur vorgenannten Feldart dürfen sich die zugeordneten Felder hinsichtlich Rang und Größe der Dimensionen unterscheiden. Speicherverwaltung und dynamische Kontrolle Ohne Benutzerintervention verläuft die Speicherzuordnung dynamisch. In einigen Fällen ist es dennoch sinnvoll, Einfluss auf die Speicherverwaltung zunehmen, z.B. wenn man sicherstellen möchte, dass lokale Variablen eines Unterprogramms nach dem Rücksprung in das aufrufende Programm ihren Definitionsstatus behalten, oder wenn in einem Unterprogramm ein Feld benötigt wird, dessen Gestalt von einer oder mehreren Variablen abhängen soll, oder wenn man nur für bestimmte Zeit ein lokales Feld benötigt, wobei die Gestalt dieses Feldes erst nach einigen Berechnungen dynamisch festgelegt werden kann. Im ersten Fall kann man COMMON-, DATA- oder SAVEAnweisungen verwenden, bzw. die entsprechenden Variablen gleich bei Initialisierung mit dem SAVE-Attribut versehen. Im zweiten Fall ist ein automatisches Feld sinnvoll, im dritten Fall bieten sich dynamische Felder oder Feldzeiger an. Ein automatisches Feld wird beim Aufruf des Unterprogramms erzeugt. Dabei wird der Speicherplatz für das jeweilige automatische Feld zugeteilt. Beim Rücksprung in das aufrufende Unterprogramm wird das Feld wieder gelöscht, d.h. der Speicherplatz automatisch wieder freigegeben. Ein dynamisches Feld wird im Spezifikationsteil eines (Unter-)Programms deklariert. Dabei wird auf Indexgrenzen verzichtet, statt dessen wird dieses dynamische Feld mit einem ALLOCATABLE-Attribut ausgestattet. Es existiert so lange noch nicht, bis im Verlauf der Ausführung einer Programmeinheit feststeht, welche Gestalt das Feld haben soll. Dann wird mit Hilfe der ALLOCATE-Anweisung die explizite Gestalt des Feldes spezifiziert und der Speicherplatz angefordert, d.h. das Feld wird dynamisch erzeugt. Es existiert so lange, bis es mit der DEALLOCATE-Anweisung wieder gelöscht, der Speicherplatz also freigegeben wird. Wird ein dynamisches Feld vor dem Rücksprung 9 Kapitel 3: Ausgewählte Features von Fortran aus seiner Programmeinheit in die aufrufende Programmeinheit nicht gelöscht, ist der Existenzstatus „undefiniert“. Feldzeiger. Wie bei dynamischen Feldern, sind für einen Feldzeiger keine Indexgrenzen spezifiziert. Die Deklaration des Feldzeigers wird aber auch im Spezifikationsteil eines (Unter-)Programms vorgenommen, wobei die Kennzeichnung als Feldzeiger über das POINTER-Attribut geschieht. Erst bei Ausführung seines (Unter-)Programms werden die Indexgrenzen, d.h. die Gestalt des Feldes, festgelegt und der entsprechende Speicher angefordert. Die Erzeugung eines Zielfeldes wird durch eine ALLOCATE-Anweisung erreicht. Solange diese nicht erfolgt, der Zeiger also nicht zugeordnet ist, ist die Gestalt des Feldzeigers undefiniert. 3.6 Zuordnungen Die Programmeinheiten sollen natürlich nur in den seltensten Fällen allein für sich, d.h. ohne Kommunikation miteinander, ablaufen. Datenobjekte, Unterprogrammnamen und andere Objekte werden zwischen den Programmeinheiten weitergereicht. Dabei gibt es in Fortran folgende Möglichkeiten: 3.6.1 Zuordnung über Namen Unter Parameterzuordnung versteht man die Weitergabe von Datenobjekten und externer Unterprogrammnamen mittels aktueller und formaler Parameter. Die in einer Funktions-Deklaration eingeführten Argumente werden als Formalparameter bezeichnet. Die vom aufrufenden Programm übergebenen Werte nennt man Aktualparameter. Zu jedem Aktualparameter muss ein Formalparameter existieren, und Aktual- und Formalparameter müssen hinsichtlich ihres Typs übereinstimmen. Die Parameterübergabe erfolgt bei Fortran über Adressen, Aktualparameter und Formalparameter sind also identisch, ohne Kopie. Bei Konstanten und Ausdrücken wird eine Hilfsvariable übergeben (nur in Eingaberichtung), wenn eine Überschreibungsgefahr besteht. Umgebungszuordnung (host association). Ein Unterprogramm ist in eine größere Programmeinheit eingebettet und kennt alle (bzw. viele) Daten dieser Umgebung. Lokale Größen überschreiben dabei allerdings die Größen, die aus der Umgebung übernommen wurden. 10 Kapitel 3: Ausgewählte Features von Fortran USE-Zuordnung. Gemeinsam genutzte Objekte werden in Modulen angeordnet. Die Deklarationen im Modul-Spezifikationsteil und die expliziten Schnittstellen zu den Modul-Unterprogrammen können anderen Geltungseinheiten mit Hilfe der USEAnweisung zur Compilezeit zugänglich gemacht werden (abhängige Compilation). 3.6.2 Zuordnung über Zeiger Durch Zeigerzuordnung werden ein Zeiger und sein Ziel einander so zugeordnet, dass das Ziel benutzt oder definiert werden kann, indem man dazu auf den Zeiger (und nicht auf das Ziel) Bezug nimmt. Ein Zeiger kann bei Bedarf einem anderen Ziel zugeordnet werden, und die Zuordnung kann auch jederzeit aufgehoben werden. Ein Zeiger ist zu einem bestimmten Zeitpunkt immer nur höchstens einem Ziel zugeordnet. Umgekehrt kann ein Ziel jederzeit mehreren Zeigern zugeordnet sein (vgl. Kap. 3.4). 3.6.3 Zuordnung über den Speicher Datenobjekte sind über den Speicher einander zugeordnet, wenn sich ihre Speicherfolgen mindestens eine gemeinsame Speichereinheit teilen, oder wenn ihre Speichereinheiten unmittelbar aufeinander folgen. Wenn zwei Speicherfolgen eine bestimmte Speichereinheit gemeinsam belegen, dann sind (soweit die Längen der jeweiligen Speicherfolgen das zulassen) auch die jeweils davor liegenden Speichereinheiten einander zugeordnet. Entsprechendes gilt auch für die jeweils nachfolgenden Speichereinheiten der beiden Speicherfolgen. COMMON-Blöcke. Jeder gemeinsam genutzte Speicherbereich hat eine Speicherfolge, die aus den Speicherfolgen aller Variablen besteht, die sich in ihm befinden. COMMONBlöcke dienen dem Austausch von Variablen (nicht Formalparameter oder Funktionsresultate) zwischen Programmeinheiten. Im Grunde ist ein COMMON-Block ein Stück Hauptspeicher, das jedes Teilprogramm, falls erforderlich, mitbenutzen darf, die gemeinsamen Speicherblöcke sind also global. Die Reihenfolge der einzelnen Speicherfolgen ist durch die Reihenfolge der entsprechenden Variablen gegeben. Die COMMON-Anweisung steht in jedem Teilprogramm, das auf den gemeinsamen Speicherraum zugreift: COMMON [/[CNAME]/] VARIABLE[,VARIABLE]... Es gibt einen „blanc common“ (ohne CNAME-Attribut) und bei Bedarf viele „named common“ mit eindeutigen Bezeichnern (CNAMEs). Die Variablen im „blanc common“ können im Gegensatz zu denen im „named common“ nicht initialisiert werden. Die Lebensdauer 11 Kapitel 3: Ausgewählte Features von Fortran des „blanc common“ ist damit so lang wie die des Hauptprogramms. Dagegen wird der Speicherplatz eines nicht mit SAVE gesicherten „named common“ für andere Benutzung freigegeben, wenn keines der Teilprogramme mehr aktiv ist, das auf ihn referenziert. Diese Art der Speichernutzung ist allerdings ein „Relikt“ aus den älteren Fortran-Zeiten und fehleranfällig bzw. wenig überschaubar. Fortran 90-Programme sollten über Module als die bessere Alternative implementiert werden. EQUIVALENCE. Über die EQUIVALENCE-Anweisung werden zwei oder mehr Größen einer Programmeinheit einander zugeordnet, indem sie auf dieselbe Speicherfolge verweisen. So kann dieselbe Information durch verschiedene Namen referenziert werden. Die EQUIVALENCE-Anweisung führt allerdings keine Typkonversionen durch und impliziert auch keine Äquivalenz im mathematischen Sinn, sondern assoziiert lediglich Speichereinheiten. Die folgende Abbildung verdeutlicht die Art der Zuordnung von bspw. zwei Feldern. DIMENSION M(3,2),P(6) !zweidimensionales Feld M mit 6 !Elementen, und eindimensionales !Feld P mit 6 Elementen EQUIVALENCE (M(2,1),P(1)) !Assoziiert M und P M(1,1) M(2,1) M(3,1) M(1,2) M(2,2) M(3,2) P(1) P(2) P(3) P(4) P(5) P(6) Abb. 3.1: Assoziation zweier Felder mit EQUIVALENCE Durch das Äquivalent-Setzen wird der gemeinsame Speicher bereich u. U. nach hinten erweitert. Gemeinsame Speicherbereiche dürfen jedoch nie nach vorn erweitert werden. 3.7 Ein- und Ausgabe Der Grundbaustein des Fortran-Dateisystems ist der Datensatz, d.h. eine Folge von Werten bzw. Zeichen (z.B. eine Zeile auf dem Bildschirm oder in einer Datei) mit fester Länge. Es wird zwischen formatgebundenen (listengesteuerten) und formatfreien Datensätzen unterschieden, was Einfluss auf die auszuführenden E/A-Anweisungen hat – formatgebundene Datensätze können nur mit formatgebundenen Anweisungen (d.h. unter Verwendung des FMT-Attributs) bearbeitet werden, analog verhält es sich bei 12 Kapitel 3: Ausgewählte Features von Fortran formatfreien Datensätzen. Eine dritte Art von Datensätzen ist der Dateiendesatz (ENDFILE) [Ge91]. Als Datei wird die Folge zusammengehörender Datensätze bezeichnet. Sie kann auf externen Medien abgelegt sein, oder intern als Arbeitsspeicherbereich existieren. Es gibt zwei Zugriffsmethoden: Bei sequenzieller Verarbeitung ist durch die Reihenfolge, in der die Datensätze in die Datei geschrieben werden, auch die Reihenfolge vorgegeben, in der die Datensätze wieder gelesen werden können, d.h. der n-te Datensatz kann erst nach dem Lesen der (n – 1) vorherigen Datensätze gelesen werden. Wenn eine externe Datei für den direkten Zugriff geöffnet ist, dann können Datensätze unabhängig von der Reihenfolge der Datensatznummern geschrieben werden. Die Datensatznummer wird intern vom Speichermedium verwaltet – die Adresse eines Datensatzes auf dem Speichermedium kann als Produkt der Datensatznummer und der (festen) Länge eines Datensatzes berechnet werden. Als Ein- und Ausgabeanweisungen sehen zur Verfügung: READ WRITE PRINT Datenübertragung OPEN CLOSE INQUIRE Dateistatus BACKSPACE ENDFILE REWIND Positionierung OPEN (11, FILE=’x’, ACCESS=’DIRECT’, FORM=’FORMATTED’, RECL=80) öffnet bspw. eine Datei „x“ formatgebunden mit direktem Zugriff und einer Datensatzlänge von 80 Zeichen und verbindet diese mit der E/A-Einheit 11. Als weitere sinnvolle Parameter lassen sich Anweisungsmarken angeben, zu denen gesprungen wird, wenn z.B. ein Fehler auftritt (ERR=20) oder das Dateiende erreicht ist (END=30). Die Funktion INQUIRE dient der Abfrage des Zustands von Dateien und auch Ein- und Ausgabeeinheiten. Ausschließlich für sequenzielle Dateien existieren die drei Positionierungs-Anweisungen BACKSPACE (= Zurücksetzen der Datei um einen Datensatz), REWIND (= Rücksetzen an den Dateianfang) und ENDFILE (= Ende der Datei). Formatierung Die Anweisung FORMAT legt in umfangreicher Weise die Formatierung sowohl eingelesener, als auch auszugebender Daten fest. Alle verfügbaren Formatspezifikationen an dieser Stelle aufzuzählen, würde den Rahmen dieser Arbeit sprengen. Abgesehen davon erscheint die Formulierung der Format-Attribute auf den ersten Blick recht kryptisch. Festhalten lässt sich, dass beim Einlesen die Daten in eine 13 Kapitel 3: Ausgewählte Features von Fortran interne Binärdarstellung konvertiert werden, bei der Ausgabe werden sie aus ihrer internen binären Darstellung entsprechend der FORMAT-Anweisung in Zeichenreihen umgewandelt [Ge91]. Formatgebundene Sätze enthalten die Daten in Zeichendarstellung, so gut wie immer in menschenlesbarer Form. Allerdings kostet die Umwandlung von der internen Darstellung in die formatierte Darstellung und umgekehrt ihre Zeit. Bei formatfreier Übertragung wird die interne Darstellung des benutzten Rechners nicht umgewandelt. Vorteil ist also die Schnelligkeit und Genauigkeit der Abspeicherung. Auch der Platzbedarf ist z. T. erheblich geringer: eine Double-Zahl von 8 Byte interner Darstellung braucht z.B. zur fehlerfreien Wiedergabe ein FORMAT es24.17, also dreimal soviel Platz. Dienen daher die Daten doch wieder nur zur Computerverarbeitung, ist die formatfreie Art vorzuziehen. Ein Problem ergibt sich aufgrund der Umwandlung zwischen den internen Zahlendarstellungen vieler Rechner, wenn formatfreie Dateien zwischen Rechnern mit unterschiedlichen Zahlendarstellungen ausgetauscht werden sollen [Gr99]. Praktischerweise kann eine Formatspezifikation auch mit einer Anweisungsmarke versehen werden, so dass sie immer wieder genutzt werden kann. Eine ausführliche Beschreibung aller Formatspezifikationen geben [Ge91] oder auch [UH84]. 14 Kapitel 4: Anwendungsgebiete von Fortran-Systemen 4 Anwendungsgebiete von Fortran-Systemen Die Programmiersprache Fortran wird bevorzugt zur analytischen und numerischen Berechnung natur- und ingenieurwissenschaftlicher Probleme benutzt. Wesentlich sind hohe Ausführungsgeschwindigkeit und gute Lesbarkeit – auch für Nicht-Profis. Fortran ist die älteste höhere Programmiersprache, steht aber zu Unrecht in dem Ruf, veraltet zu sein. Die Ursachen für das Überleben sind recht nahe liegend: ein großer Nutzerkreis und die Menge an fertigen und laufenden Programmen – für nahezu alle wissenschaftlichen Problemkreise existieren umfangreichste Bibliotheken, die auch heute noch unentbehrlich sind. Parallele Programmierung Hierbei ist das Umdenken von sequenzieller zu paralleler Bearbeitung eines Problems entscheidend. Es stehen sich SIMD (Single Instruction Multiple Data)- und MIMD (Multiple Instruction Multiple Data)-Ansätze gegenüber. SIMD bedeutet, dass eine (einzige) Operation auf mehrere Datenbereiche angewendet wird. Eine Sprache mit MIMD-Features erlaubt es, simultan unterschiedliche Funktionen oder Subroutinen auf verschiedenen Datenbereichen operieren zu lassen. Die hohe Effizienz von Fortran legt nahe, dass es sich für die Parallelisierung eignet, und es gibt eine Reihe von Forschungsprojekten, die Fortran z.B. für die Simulation komplexer Systeme einsetzen. Fortran 90 selber hat allerdings wenige bis gar keine MIMD-Konstruktionen [PTVF96]. Fortran 95 als Weiterentwicklung von Fortran 90 bietet durch forall- und PUREKonstrukte bessere Möglichkeiten, MIMD-Features zu nutzen [PTVF96]. In einer anderen Weiterentwicklung von Fortran 90, der Programmiersprache High Performance Fortran (HPF) für die datenparallele Programmierung sind MIMD-Features gezielt implementiert. Sie hat eine Reihe industrieller Anwendungen ermöglicht, zu denen u. a. Deformationsberechnungen von Automobilteilen, Strömungssimulation von Flugzeugteilen, Staudammberechnungen, die Berechnung elektromagnetischer Felder, umfangreiche Wetterprognosen und viele weitere gehören [PTVF96]. OpenMP („Open Multi Processing“) ist eine Sammlung von Compiler-Direktiven, Bibliotheksroutinen und Umgebungsvariablen, die Parallelisierung durch gemeinsame Speichernutzung in Fortran- und C/C++-Programmen unterstützt. Die Parallelisierung läuft zum Großteil über parallele Schleifen ab. Dabei wird nach dem Fork-Join-Prinzip 15 Kapitel 4: Anwendungsgebiete von Fortran-Systemen gearbeitet, d.h. das Problem wird in parallelen Bereichen auf mehrere CPUs verteilt, die einzelnen Ergebnisse an der nächsten seriellen Stelle wieder zu einem gemeinsamen Ergebnis zusammengeführt: Serieller Bereich 0 Paralleler Bereich 0 Serieller Bereich 0 Paralleler Bereich 0 Serieller Bereich 0 Master Thread 1 2 3 Team of Threads Master Thread 1 2 3 Team of Threads Master Thread Abb. 4.1: Fork-Join-Prinzip (Quelle: [UH03]) 16 Kapitel 5: Quick Sort 5 Quick Sort In Anlehnung an den Pseudo-Code, der in [OW96] vorgestellt wird, ist es recht einfach, den Quick Sort-Algorithmus in Fortran zu implementieren. Als Entwicklungsumgebung wurde Compaq Visual Fortran 6.6 Professional eingesetzt, eine Anwendung mit großem Funktionsumfang, um auch größere Fortran-Projekte zu verwalten und zu debuggen. Es wurde ein Fortran 90-Compiler verwendet. Ausgeführt wurde das fertige Programm auf einem Intel Celeron II mit 433 MHz und 256 MByte Arbeitsspeicher. Die Struktur des Proramms gliedert sich zunächst in ein Modul qsort_modul und das Hauptprogramm Quick_Sort. Das qsort_modul enthält wiederum zwei öffentliche und eine private Subroutine: Die private Subroutine partition sorgt für das Weiterbewegen der Zeiger, den paarweisen Elementevergleich des zu testenden Arrays und das Vertauschen der Elemente, falls erforderlich. Aufgerufen wird partition von der öffentlichen Subroutine qsort_sub, die rekursiv auf alle Teilbereiche des übergebenen Arrays angewendet wird, solange deren Länge größer Null ist. Die dritte Subroutine zufall startet einen Zufallsgenerator, der das übergebene Array mit Werten füllt. Eingabeparameter ist die Länge, die im Hauptprogramm bestimmt wird, Rückgabewert ist das fertig gefüllte Array. Hierfür wurde eine von Fortran vordefinierte Subroutine random_number zur Erzeugung einer Pseudozufallszahl verwendet, die in einer DO-Schleife so oft ausgeführt wurde wie das Array Elemente hat. Damit ist das Modul qsort_modul zu Ende. Es folgt das Hauptprogramm Quick_Sort, in dem zunächst die Subroutinen des vorgenannten Moduls mittels USE qsort_modul eingebunden werden. Anschließend folgt der Spezifikationsteil, in dem alle später verwendeten Variablen deklariert werden. Hier besteht in der Variable laenge die Möglichkeit, die Größe des zu sortierenden Arrays festzulegen. Dieses wird sogleich durch Aufruf der zufall-Subroutine mit entsprechenden Werten gefüllt. Um die Performance des Sortiervorgangs zu messen, wird die Zeit anhand eines auf der Uhr des Rechners basierenden Funktion gemessen. Diese wird erst initialisiert (auf Null gesetzt), wobei der Parameter count_rate spezifiziert, wie oft pro Sekunde der Wert hochgezählt werden soll (im vorliegenden Fall von count_rate=1000 werden also Millisekunden gezählt). Anschließend wird der Zählvorgang gestartet und der Startzeitpunkt in der Variablen startzeit gespeichert. Dann folgt der Aufruf der Subroutine qsort_sub, d.h. das Array wird sortiert. Zuletzt wird wiederum die Zeit 17 Kapitel 5: Quick Sort gemessen und die Differenz gebildet. So kann schließlich die aufgewendete Zeit auf dem Bildschirm ausgegeben werden. Der vollständige Quellcode kann Anhang A entnommen werden. Für den Vergleich mit Java ist die von [Ku03] erstellte Klasse verwendet und um einen Zufallsgenerator und die Zeitmessung erweitert worden. Der Zufallsgenerator und das sukzessive Füllen des Testarrays mit Zahlen lässt sich über Random() und eine Schleife realisieren: Random r = new Random(); int[] a = new int[5000000]; for (int i = 0; i < a.length-1; i++) a[i] = r.nextInt(); Die Zeitmessung wurde mittels System.currentTimeMillis() bewerkstelligt, was wie in der zuvor besprochenen Fortran-Version vor und nach dem Sortieren ausgeführt wird, wobei dann die Differenz den Zeitaufwand zeigt. Um den Algorithmus in Java zu testen, wurde die Entwicklungsumgebung Eclipse 2.1.3 verwendet. Die Ergebnisse beider Sprachen können der folgenden Tabelle entnommen werden: Ausfürungszeit [Sek] in Fortran Ausführungszeit [Sek] in Java 100 < 0,01 0,010 1.000 0,013 0,020 10.000 0,042 0,020 20.000 0,097 0,081 50.000 0,231 0,150 100.000 0,511 0,271 250.000 1,352 0,762 500.000 2,841 1,732 1.000.000 6,399 3,695 2.000.000 12,478 7,490 5.000.000 33,899 18,266 Array-Größe Tab. 5.1: Dauer der Ausführung von Quick Sort auf einem Intel Celeron II mit 433 MHz und 256 MB Arbeitsspeicher Es wird deutlich, dass – obwohl auch die in Fortran realisierte Version vergleichsweise gute Werte erzielt – Java eindeutig effizienter arbeitet. Auch der Quellcode ist in Java um einiges schlanker. Den ca. 70 reinen Lines of Code in Fortran stehen in Java lediglich ca. 25 Zeilen gegenüber. 18 Kapitel 6: Zusammenfassung und Ausblick 6 Zusammenfassung und Ausblick „Man könnte Fortran (kurz für formula translation), die ursprüngliche „High Level“Programmiersprache, für das Hightech-Äquivalent zur Keilschrift halten – immerhin ist es mittlerweile 47 Jahre her, dass IBM sie eingeführt hat. Doch sie wird immer noch breit eingesetzt, vor allem im wissenschaftlichen Bereich. Warum hat dieser Veteran aus der Eisenhower-Ära so viele Hardware- und Softwaregenerationen überlebt? „Teilweise ist es die Lernkurve“, sagt Hans Boehm von den Hewlett-Packard Laboratories, früherer Vorsitzender der Programmiersprachen-Gruppe in der Association for Computing Research. „Für einige Leute ist sie gut genug, und es ist schwierig, etwas aufzugeben, was man einmal gelernt hat. Die Anpassbarkeit und Kompatibilität, die Fortran zur Lingua Franca des Programmierens in den 60er und 70er Jahren gemacht hat, spielen ebenfalls eine Rolle für ihre Langlebigkeit. Größere Upgrades haben ihre Effizienz verbessert und neue Features gebracht, dabei aber die älteren Versionen intakt gelassen. Also funktioniert eine riesige Menge von erprobten Fortran 77-Programmen noch mit dem gegenwärtigen Fortran 90. So macht man das, Microsoft!“ [Sc04] Die anderen Technologien waren übrigens: Analoguhren, Nadeldrucker, Schreibmaschinen, Hörfunk, Pager, Tonbänder, Vakuumröhren, Faxgeräte und Mainframe-Computer. Aufgrund der Tatsache, dass in der neusten Fortran-Version auch bereits objektorientierte Konzepte integriert worden sind, und aufgrund der bekannten Vorteile, die die Sprache schon über einen so langen Zeitraum haben erfolgreich sein lassen, wird sie wohl auch in Zukunft weiterleben und in den bekannten Anwendungsgebieten und der Forschung von großer Bedeutung sein. Ein weiteres Upgrade (Fortran 2000) ist geplant, die Ankündigungen auf verschiedenen Websites weichen aber bzgl. der Veröffentlichung voneinander ab. 19 Anhang A: Titel von Anhang 1 A Fortran 95-Quellcode für Quick Sort (rekursiv) module qsort_modul ! Modul, das vom Hauptprogramm ! genutzt wird. public :: qsort_sub public :: zufall private :: partition ! sichtbare Quicksort-Subroutine ! sichtbarer Zufallsgenerator ! interne Sortier-Subroutine contains recursive subroutine qsort_sub(array) real, intent(in out), dimension(:) :: array integer :: iq if(size(array) > 0) then call partition(array, iq) call qsort_sub(array(:iq-1)) call qsort_sub(array(iq+1:)) endif end subroutine qsort_sub subroutine partition(array, iq) real, intent(in out), dimension(:) :: array integer, intent(out) :: iq integer :: uzeiger, ozeiger real :: temp real :: pivot ! Pivotelement pivot = array(1) uzeiger = 0 ozeiger = size(array) + 1 do ozeiger = ozeiger - 1 do if (array(ozeiger) <= pivot) exit ozeiger = ozeiger - 1 end do uzeiger = uzeiger + 1 do if (array(uzeiger) >= pivot) exit uzeiger = uzeiger + 1 end do if (uzeiger < ozeiger) then ! array(uzeiger) und array(ozeiger) vertauschen temp = array(uzeiger) array(uzeiger) = array(ozeiger) array(ozeiger) = temp else iq = ozeiger return endif end do end subroutine partition 20 Anhang A: Titel von Anhang 1 ! Der Zufallsgenerator füllt in einer DO-Schleife das Test-Array ! mit der im Hauptprogramm übergebenen Länge sukzessive mit ! Werten. subroutine zufall(ein_array, laenge) integer, intent(in) :: laenge real, intent(out), dimension(:) :: ein_array real x do z = 1, laenge call random_number(harvest=x) ein_array(z) = x end do end subroutine zufall ! Eingabe ! Ausgabe ! Zufallszahl ! Zufallsgeneratorfunktion end module qsort_modul program Quick_Sort use qsort_modul ! Hauptprogramm integer :: startzeit, endzeit, rate real :: zeit ! laenge ist die Anzahl der zu sortierenden Werte integer, parameter :: laenge = 2000000 real, dimension(laenge) :: ein_array ! Aufruf der Zufalls-Subroutine, um das Test-Array mit der ! angegebenen Zahl an Elementen zufällig zu füllen call zufall(ein_array, laenge) ! Für die Performance-Messung wird vorher die Systemzeit ! gemessen, rate = 1000 gibt an, dass der Zähler 1000mal ! pro Sekunde hochgezählt werden soll. rate = 1000 call system_clock(count_rate=rate) call system_clock(count=startzeit) ! Aufruf der rekursiven QuickSort-Subroutine call qsort_sub(ein_array) ! Zeitmessung nach dem Sortieren und Differenzermittlung call system_clock(count=endzeit) zeit = (endzeit - startzeit) / real(rate) print *, "Sortiert: ", ein_array print *, "In ", zeit, " Sekunden" end program Quick_Sort 21 Literaturverzeichnis [Ge91] Wilhelm Gehrke: Fortran 90: Referenz-Handbuch, Hanser-Verlag, 1991. [UH84] Regionales Rechenzentrum für Niedersachsen (RRZN), Universität Hannover (Hrsg.): FORTRAN 77 Sprachumfang, Hannover, 1984. [CS70] Harry L. Colman, Clarence Smallwood: Fortran – Problem-orientierte Programmiersprache, KUNST UND WISSEN Erich Bieber, Stuttgart, 6. Aufl., 1970. [Gr99] G. Groten: Fortran 90/95; Kurs für die Ausbildung von MTAs, http://www.fz-juelich.de/zam/docs/bhb/bhb_html/d0124/, Datum: 2004-05-02, Forschungszentrum Jülich, 1999. [OW96] Thomas Ottmann, Peter Widmayer: Algorithmen und Datenstrukturen, Spektrum Akademischer Verlag, 3. Aufl., 1996. [PTVF96] William H. Press, Saul A. Teukolsky, William T. Vetterling, Brian P. Flannery: Numerical Recipes in Fortran 90 2nd Edition, Volume 2: The art of parallel scientific computing, Cambridge University Press, 1996. [UH03] Regionales Rechenzentrum für Niedersachsen (RRZN), Universität Hannover: Parallele Programmierung mit OpenMP, http://www.rrzn.unihannover.de/fileadmin/ful/vorlesungen/kolloquium/ss_03/open_mp.pdf, Datum: 2004-04-27, Hannover, 2003. [Sc04] Eric Scigliano: 10 Technologien, die überlebt haben, Technology Review – Das M.I.T.-Magazin für Innovation, Nr. 3 März 2004, S. 96-99. [Ku03] Herbert Kuchen: Praktische Informatik; QuickSort Quellcode, http://www.wi.uni-muenster.de/pi/lehre/SS03/info2/quicksort.java, Datum: 2004-05-03