Fortran – Hardware optimal nutzen

von DI Dr. Christoph Hofer

Der Name Fortran setzt sich zusammen aus “FORmel TRANslator” und ist eine der ältesten Programmiersprachen. Für viele Softwareentwickler ist sie der Archetyp für eine alte, behäbige, eingeschränkte und schwer zu verstehende Programmiersprache mit der man am besten nichts zu tun haben möchte. Für die alten Versionen von Fortran mag dieses Vorurteil wirklich wahr sein. Fortran hat sich in seiner langen Geschichte aber viel verändert, sodass in seiner “modernen” Variante (wie etwa Fortran 2003) die Sprache einen viel schlechteren Ruf hat als ihr zusteht. Der typische Use-Case für Fortran als Programmiersprache sind rechenintensive numerische Simulationen, wie etwa Wettervorhersagen, Strömungssimulationen, Stabilitätsberechnungen, uvm.

Aus alt mach neu

Fortran gilt als die erste jemals realisierte höhere Programmiersprache und wurde in den Jahren 1954 – 1957 von IBM entwickelt (FORTRAN I). Der Umfang der Sprache war noch sehr eingeschränkt, zum Beispiel gab es nur Integer (Ganzzahlen) und Reals (Gleitkommazahlen) als Datentypen und noch keine Funktionen. In den folgenden Jahren wurden neue verbesserte und umfangreichere Fortran Versionen entwickelt (FORTRAN II, FORTRAN III, FORTAN 66). Das nächste große Update bekam Fortran im Jahr 1977 (FORTRAN 77). Durch neue Features in der Sprache wurde diese Version sehr populär und damit schnell zu “dem” Fortran. Auch heute noch ist, wenn über Fortran Code gesprochen wird, hauptsächlich FORTRAN 77 Code gemeint, was auch die vielen Vorurteile gegenüber der Sprache erklärt. Seitdem gab es noch einige Updates der Sprache, die sie an moderne Programmierkonzepte und Standards heranführen.

Große Meilensteine in der Entwicklung waren die Updates zu Fortran 90 und Fortran 2003, welche neben der Namensänderung (FORTRAN → Fortran) gängige Konzepte wie unter anderem freie Sourcefile Formate,  Module, Operator Overloading, Derived data types, Pointers und objektorientiertes Programmieren  zur Programmiersprache hinzufügten. Zusätzlich dazu gab es mit Fortran 95 und Fortran 2008 jeweils ein kleines Update der Sprache. Die aktuellste Version des Fortran Standards ist Fortran 2018, wobei noch kein Compilerhersteller alle Features unterstützt.

Höhere Programmiersprachen und Compiler

Mikroprozessoren werden einer sogenannten Maschinensprache programmiert, also ein Binärcode der vom Mikroprozessor als Folge von Befehlen (Instructions) interpretretiert wird. Da diese Sprachen sehr von der verwendeten Hardware abhängen und das direkte Programmamieren in einer Maschinensprache sehr aufwändig ist war die Entwicklung von höheren Programmiersprachen und Compilern ein großer Fortschritt. Höhrere Programmiersprachen verwenden mathematische Ausdrücke oder Ausdrücke die an eine natürliche Sprache (meist Englisch) angelehnt sind die mittels einem Compiler (und Linker) in Maschinensprache übersetzt werden. Höhere Programmiersprachen sind unabhäng von der Hardware, die Anpassung an die konkrete Hardware wird vom Compiler übernommen.

Diesen Beitrag mit Ihrem Netzwerk teilen:

Operator Overloading

ist eine Programmiertechnik mit der die Bedeutung von Operatoren (wie etwa +, -, *, …) von den jeweiligen verwendeten Typen abhängt. Beispielsweise liefert 1 + 2 die Zahl 3, aber “Helllo ” + “World” den String “Hello World”.

Diesen Beitrag mit Ihrem Netzwerk teilen:

Derived Data Type

erlauben es dem Benutzer selbst Datentypen aus bestehenden Typen zu definieren. Dies bietet somit die Möglichkeit für die Definition von logisch zusammengehörenden Daten in einem Typ und die Wiederverwendung derer in verschiedenen Teilen des Programms.

Diesen Beitrag mit Ihrem Netzwerk teilen:

Pointer

ist ein Datentyp, der die Speicheradresse einer Variable speichert anstatt die Variable selbst. Pointer verweisen (referenzieren) sozusagen auf eine Speicheradresse, das Extrahieren des dahinterliegenden Wertes wird als Dereferenzierung bezeichnet. Im Unterschied zu Pointern in C/C++ besitzen Fortran Pointer noch weitere Informationen und erlauben auch auf nicht zusammenhangende Speicherbereiche (im Fall von Arrays) zu verweisen.

Diesen Beitrag mit Ihrem Netzwerk teilen:

Objektorientiertes Programmieren

ist eine Programmierstil in dem Daten nicht nur in Derived Data Types gesammelt werden sondern zusammen mit Logik und Funktionalität in soganannten Objekten gekapselt. Jedes Objekt hat definierte Schnittstellen über jene es mit anderen Objekten interagieren kann, z.B. sind oft nicht alle Daten und Funktionen eines Objekts für andere Objekte sichtbar. Ziel ist es Codeduplikationen zu vermeiden um das Fehlerpotential und den Wartungaufwand zu verringern.

Diesen Beitrag mit Ihrem Netzwerk teilen:

Parallelisierung: gewusst wie

Da der Trend bei der Entwicklung neuer CPUs eindeutig in Richtung von immer höherer Anzahl von Prozessorkernen geht, ist effiziente Parallelisierung ein integraler Teiler für hochperformanten Code. Ziel ist es alle Kerne mit der annähernd selben Rechenlast zu versorgen und durch intelligentes Design des Programmcodes die Kommunikationen bzw. den Datenaustausch zwischen den Kernen möglichst gering zu halten. Gängige Möglichkeiten zur Parallelisierung von Programmen auf einer CPU bieten die beiden Möglichkeiten OpenMP und MPI, als auch die neu in den Fortran Standard hinzugefügten Coarrays.

Fortran 2 RISC Software GmbH C IStock AKodisinghe

OpenMP

OpenMP ist konzipiert für shared memory Systemen wie etwa Desktop PCs, oder shared memory Großrechner. Auf technischer Ebene werden Threads durch ein sogenanntes “fork-join” Modell zu Beginn einer OpenMP Parallel Region erzeugt (fork) und am Ende dieser wieder zusammengeführt (join). OpenMP bietet eine einfache Möglichkeit um ohne große strukturelle Änderungen am Programm gewisse Teile, wie etwa Programmschleifen parallel ablaufen zu lassen. In C/C++ ist dies über “#pragma” realisiert, in Fortran mithilfe der speziellen Syntax “!$”. Durch die einfache Art des Einbaus, bleibt der Code leicht verständlich und die Gefahr durch Bugs in der Entwicklung ist gering. Die parallele Skalierbarkeit ist aber durch das häufige erzeugen der Threads, sowie durch das Synchronisieren von gemeinsamen (shared) Variablen in den Caches der CPU oft begrenzt.

“fork-join”-Model bei OpenMP

MPI

MPI (Message Passing Interface) basiert auf einem distributed memory Modell, wie etwa Rechencluster, welche aus mehreren Rechnern bzw. CPUs bestehen. Im Gegensatz zu OpenMP werden anstatt Threads hier Prozesse erzeugt, die unabhängig voneinander operieren und auch keine gemeinsamen (shared) Variablen besitzen. Prozesse müssen nicht auf derselben CPU operieren, darum sind sie für distributed memory Systeme geeignet, können aber auch auf shared memory Systemen verwendet werden. Um Daten und Information zwischen Prozessen auszutauschen, werden Nachrichten (messages) von zwischen Prozessen versandt. Diese Prozesse werden zu Beginn des Programmstarts erzeugt, und bleiben währen der ganzen Laufzeit bestehen. Weiters müssen keine Variablen, die in den Caches der CPU abgelegt sind synchronisiert werden, was MPI üblicherweise viel skalierbarer macht als OpenMP. Der Nachteil sind aber unter normalen Umständen große strukturelle Umbauarbeiten des Codes, was auch die Gefahr von neuen Bugs durch den Umbau stark erhöht. MPI erlaubt auch eine Kommunikation von unterschiedlichen Programmen, daher folgt es dem sogenannten multiple programs multiple data (MPMD) Modell.

Coarray

Coarray Fortran wurde im Fortran 2008 Standard der Sprache hinzugefügt. Wie MPI unterstützen Coarrays sowohl distributed als auch shared memory Systemen. Coarrays funktionieren nach dem single programm multiple data (SPMD) Modell, ein Programm erzeugt mehrere Kopien von sich selbst (images), wobei jedes parallel und unabhängig voneinander abläuft. Die Kommunikation wird erneut über Nachrichten ermöglicht. Im Gegensatz zu MPI ist die Programmierung mit Coarrays intuitiver, da die Schnittstelle für den User abstrahierter sind und weniger “low-level” Wissen notwendig ist.  Da MPI in C implementiert ist müssen auch oft Daten durch die Fortran/C Schnittstelle kopiert werden, so wie etwas array-slices. Hingegen sind Coarrays nativ in Fortran programmiert und können auch effizienter mit den Fortran spezifischen Datentypen umgehen. Nichtsdestotrotz sind Coarrays noch relativ jung in Fortran und nicht so ausgereift wie MPI.

Leider existiert in Fortran kein Modul für “Threading”, welches ähnlich der “multithreading” Bibliothek in C++ ist, die mit dem Erscheinen des C++ 11 Standards zu C++ hinzugefügt wurde.

 

Autor

DI Dr. Christoph Hofer ist Professional Software Engineer in der Unit Industrial Software Applications.

Mehr Fachbeiträge zu Fortran folgen in den nächsten Wochen!

Diesen Beitrag mit Ihrem Netzwerk teilen: