Compiler-Direktiven zur Parallelisierung
Das Beispielprogramm linalg zeigt, daß ein globales Übersetzen aller
Routinen mit der Option ''-O3'' nicht sinnvoll ist: Der
Parallelisierungsgewinn liegt für viele Schleifen unter 1, d.h. sie
arbeiten mit vier CPUs langsamer als mit einer! Man sollte die Option ''-O3''
also zunächst nur bei den Routinen einsetzen, die dadurch insgesamt
schneller werden, und alle anderen nur vektorisieren. Weitergehende
Schritte erfordern die Verwendung von Compiler-Direktiven, die mit ''C$DIR
DIREKTIVE'' in Fortran- bzw. mit ''#pragma_CNX direktive'' in C-Programme
eingebaut werden.
Möchte man auch solche Routinen parallelisieren, bei denen nur einige
Schleifen davon profitieren, kann man den Compiler durch die Direktive
NO_PARALLEL
vor einer schlecht parallelisierenden Schleife anweisen, diese nicht zu
parallelisieren.
Es gibt eine ganze Reihe von Direktiven, mit denen man den Compiler dazu
bringen kann, eine Schleife zu parallelisieren:
- FORCE_PARALLEL
Die nächste Schleife wird parallelisiert, auch wenn Abhängigkeiten
zwischen den Iterationen bestehen!
- FORCE_PARALLEL_EXT
Wie FORCE_PARALLEL, aber das Vertauschen der nächsten Schleifen bleibt
erlaubt, so daß die tatsächlich parallelisierte Schleife auch die innere
sein kann.
- PREFER_PARALLEL, PREFER_PARALLEL_EXT
analog zu oben, aber wenn der Compiler Abhängigkeiten entdeckt, wird
nicht parallelisiert.
- SYNCH_PARALLEL
Die nächste Schleife wird parallelisiert. Bestehende Abhängigkeiten
werden durch den Einbau geeigneter Synchronisationsanweisungen
berücksichtigt.
Natürlich sind diese Anweisungen - vor allem FORCE_PARALLEL - mit Vorsicht
zu benutzen; man sollte sich hinterher immer davon überzeugen, daß das
Programm für verschiedene Eingabewerte noch richtige Ergebnisse
produziert. Da aber das genaue Verhalten eines parallelisierten Programms
lastabhängig ist, kann ein Fehler u.U. lange verborgen bleiben.
Ein typischer Anwendungsfall für diese Direktiven sind Schleifen, in denen
Routinen aufgerufen werden. Solche Schleifen werden vom Compiler
grundsätzlich nicht parallelisiert, da Seiteneffekte der Funktion, z.B.
über globale Variable, möglicherweise eine Synchronisation der
Iterationen erfordern, ohne daß der Compiler dies feststellen kann. Wenn
man weiß, daß die Routine in der Schleife keine Seiteneffekte hat, kann
man die Parallelisierung der Schleife durch eine der obigen Direktiven
erzwingen. Außerdem muß man die Funktion mit der Compiler-Option ''-re''
(''reentrant'') übersetzen, damit bei gleichzeitiger mehrfacher Ausführung
der Funktion jedes Exemplar seinen eigenen Datenbereich bekommt. Ohne diese
Option würde der Compiler nur einen Text- und Stackbereich für die
Funktion anlegen, so daß alle parallelen Versionen auf dieselben lokalen
Variablen zugreifen, was natürlich zu einem vollständigen Durcheinander
führen könnte.
Man kann auch über die Parallelisierung von Schleifen hinausgehen, indem
man eigenständige ''Tasks'' definiert, also Programmteile, die bei der
Ausführung verschiedenen Threads zugeordnet werden können. Dazu dienen
die Anweisungen
BEGIN_TASKS, NEXT_TASK, END_TASKS .
Schließlich gibt es Direktiven, mit denen man die Synchronisation von
Threads explizit steuern kann, indem man kritische Bereiche mit Hilfe von
Sperr-Variablen (''Locks'') sichert. Näheres dazu kann man dem ''FORTRAN/C
Optimization Guide'' von Convex entnehmen.
Peter Junglas 18.10.1993