Fortran: OpenMP
<< zur Fortran-Startseite | |
< LAPACK | Fortran und Tcl/Tk > |
Was ist OpenMP
[Bearbeiten]OpenMP ist die Abkürzung für "Open specifications for Multi Processing" und ist eine API für Fortran und C/C++, die zum Zwecke der parallelen Programmierung mittels Shared-Memory-Ansatz für Mehrprozessor-Systeme erschaffen wurde.
Durch Anwendung von Compiler-Direktiven und spezieller Unterprogramme wird die Abarbeitung von bestimmten Programmkonstrukten auf mehrere Threads aufgeteilt.
Der "Master Thread" mit der Nummer 0 ist in einem OpenMP-Programm standardmäßig immer aktiv. Der Programmierer bestimmt im Programmcode, wann eine Gabelung (fork) in mehrere Threads gefordert wird und wann das Ganze wieder in einen einzelnen Thread vereint werden soll (join).
OpenMP wird schon von vielen Fortran-Compilern unterstützt.
Ein einfaches Beispiel
[Bearbeiten]Fortran 90/95-Code (free source form) |
program bsp use omp_lib implicit none ! fork !$omp parallel num_threads(3) ! Das nur 1x ausgeben (beim Master Thread) if( omp_get_thread_num() == 0 ) then write( *, * ) 'Insgesamt gibt es ', omp_get_num_threads(), 'Thread(s)' end if ! Das bei jedem Thread ausgeben write( *, * ) 'Thread ', omp_get_thread_num(), 'ist aktiv' ! join !$omp end parallel ! Ausgabe: ! Insgesamt gibt es 3 Thread(s) ! Thread 0 ist aktiv ! Thread 1 ist aktiv ! Thread 2 ist aktiv end program bsp |
Unter Umständen muss das Modul omp_lib
eingebunden werden. Dieses Modul enthält die interface
für die OpenMP-Routinen. Eine mögliche Form des OpenMP-Modus ist am Ende dieses Abschnittes angegeben.
Kompilieren und Linken des Beispielprogramms:
gfortran: |
gfortran -fopenmp -o bsp bsp.f90 |
Intel Fortran Compiler: |
ifort -openmp -o bsp bsp.f90 |
Erläuterung:
- OpenMP-Direktiven werden als Kommentare gekapselt. Bei Verwendung der "free source form" lautet der erste Direktiven-Abschnitt (en. sentinel, dt. Wächter) immer
!$omp
. Groß-/Kleinschreibung spielt keine Rolle. Es folgt die Anweisung, dass sich nun das Programm gabeln soll (parallel
). Die Anzahl der gewünschten Threads wird hier explizit mittels der Optionnum_threads()
festgelegt. - Verwendete OpenMP-Funktionen:
omp_get_thread_num()
... Aktuelle Thread-Nummeromp_get_num_threads()
... Anzahl der Threads
- Beendet wird der parallele Programmteil mit
!$omp end parallel
Thread-Erzeugung: Die parallel
-Direktive
[Bearbeiten]Wie im vorigen Beispiel bereits angedeutet, wird ein "fork" (die Threaderzeugung) immer mit der Direktive
!$omp parallel [optionen]
eingeleitet und mit
!$omp end parallel
beendet. Es kann hier auch eine Reihe von optionalen Steueranweisungen angegeben werden (siehe vorheriges Beispiel und nachfolgende Beispiele).
Thread-Anzahl bestimmen
[Bearbeiten]- Festlegung im Rahmen der OpenMP-Direktive
!$omp parallel
über die Optionnum_threads( nr )
- Mittels OpenMP-Subroutinenaufruf vor dem fork:
call omp_set_num_threads( nr )
- Festlegung in der Kommandozeile vor Ausführung des Programmes, z.B.:
export OMP_NUM_THREADS=nr
- Default (normalerweise 1 Thread pro CPU)
- Dynamische Anpassung zur Programmlaufzeit per Run-Time-Environment:
call omp_set_dynamic( .true. )
Sichtbarkeit/Gültigkeit von Daten
[Bearbeiten]Aufgrund des Shared-Memory-Ansatzes werden Daten standardmäßig zwischen den Threads geteilt. Dieses Verhalten kann aber auch optional geändert werden. Mögliche Varianten für die parallel
-Direktive:
shared
... Solche Daten sind explizit in allen Threads sichtbar und gültig. Eine Änderung solcher Daten in einem Thread wirkt sich auf alle anderen Threads aus.private
... Solche Daten sind nur im aktuellen Thread sichtbar und gültig, sie werden beim Eintritt in den parallelen Programmabschnitt nicht speziell initialisiert. Änderungen dieser Werte wirken sich nicht auf nachfolgende serielle Programmteile aus.firstprivate
... Ähnlich wieprivate
. Der Unterschied zuprivate
ist, dass solcherart markierte Daten mit dem letztgültigen Wert aus dem vorhergehenden seriellen Programmabschnitt initialisiert werden.
Beispiel:
Fortran 90/95-Code (free source form) |
program bsp use omp_lib implicit none integer :: a, b, c, tnr a = 123 b = 123 c = 123 ! Seriell write( *, * ) 'Seriell:' write( *, * ) 'a = ', a write( *, * ) 'b = ', b write( *, * ) 'c = ', c write( *, * ) '-------------------------------' call omp_set_num_threads( 3 ) !$omp parallel shared( a ) private( b ) firstprivate( c ) write( *, * ) 'Parallel:' tnr = omp_get_thread_num() ! Aktuelle Threadnummer if( tnr == 0 ) then a = a + 5 b = b + 5 c = c + 5 end if write( *, * ) 'a = ', a write( *, * ) 'b = ', b write( *, * ) 'c = ', c write( *, * ) '-------------------------------' !$omp end parallel ! Seriell write( *, * ) 'Seriell:' write( *, * ) 'a = ', a write( *, * ) 'b = ', b write( *, * ) 'c = ', c end program bsp |
Ausgabe:
Seriell: a = 123 b = 123 c = 123 ------------------------------- Parallel: 0 a = 128 b = 5 c = 128 ------------------------------- Parallel: 1 a = 128 b = 0 c = 123 ------------------------------- Parallel: 2 a = 128 b = 0 c = 123 ------------------------------- Seriell: a = 128 b = 123 c = 123
Für andere OpenMP-Direktiven sind auch noch andere Sichtbarkeits- und Gültigkeitsbereiche möglich (z.B. lastprivate
).
Die do
-Direktive
[Bearbeiten]Innerhalb eines parallel
-Blocks können auch do
-Schleifen parallelisiert werden. Die Schleifendurchläufe werden auf die einzelnen Threads bzw. CPUs aufgeteilt.
Beispiel:
Fortran 90/95-Code (free source form) |
program bsp use omp_lib implicit none integer :: i, tnr call omp_set_num_threads( 3 ) !$omp parallel private( i ) !$omp do do i = 1, 20 tnr = omp_get_thread_num() ! Aktuelle Threadnummer write( *, * ) 'Thread', tnr, ':', i end do !$omp end do !$omp end parallel end program bsp |
Ausgabe:
Thread 0 : 1 Thread 0 : 2 Thread 0 : 3 Thread 0 : 4 Thread 0 : 5 Thread 0 : 6 Thread 0 : 7 Thread 1 : 8 Thread 1 : 9 Thread 1 : 10 Thread 1 : 11 Thread 1 : 12 Thread 1 : 13 Thread 1 : 14 Thread 2 : 15 Thread 2 : 16 Thread 2 : 17 Thread 2 : 18 Thread 2 : 19 Thread 2 : 20
Die Zuweisung der Schleifendurchläufe an die Threads kann gesteuert werden. Dazu wird der do
-Direktive eine schedule
-Anweisung mit dem Argument Typ und ev. auch mit dem Argument Chunk-Größe beigefügt. Als Typen sind möglich
static
dynamic
guided
runtime
Diese Bezeichnungen beziehen sich auf die Art der Thread-Erzeugung.
Beispiel:
Fortran 90/95-Code (free source form) |
program bsp use omp_lib implicit none integer :: i, tnr call omp_set_num_threads( 3 ) !$omp parallel private( i ) !$omp do schedule(static, 3) do i = 1, 20 tnr = omp_get_thread_num() ! Aktuelle Threadnummer write( *, * ) 'Thread', tnr, ':', i end do !$omp end do !$omp end parallel end program bsp |
Ausgabe:
Thread 0 : 1 Thread 0 : 2 Thread 0 : 3 Thread 0 : 10 Thread 0 : 11 Thread 0 : 12 Thread 0 : 19 Thread 0 : 20 Thread 1 : 4 Thread 1 : 5 Thread 1 : 6 Thread 1 : 13 Thread 1 : 14 Thread 1 : 15 Thread 2 : 7 Thread 2 : 8 Thread 2 : 9 Thread 2 : 16 Thread 2 : 17 Thread 2 : 18
Eine do while
-Schleife kann nicht auf diese Art und Weise mittels OpenMP-Direktive parallel ausgeführt werden.
Die sections
-Direktive
[Bearbeiten]Auch die Festlegung, dass bestimmte Programmabschnitte auf je einen Thread verteilt werden sollen, ist möglich. Dazu wird das Konstrukt
!$omp sections [optionen] !$omp section block !$omp section block ... !$omp end sections
innerhalb eines parallel
-Blocks eingesetzt.
Beispiel:
Fortran 90/95-Code (free source form) |
program bsp use omp_lib implicit none integer :: a, b a = 20 b = 30 call omp_set_num_threads( 3 ) !$omp parallel shared( a, b ) !$omp sections !$omp section write( *, * ) omp_get_thread_num(), a write( *, * ) omp_get_thread_num(), "---" !$omp section write( *, * ) omp_get_thread_num(), b write( *, * ) omp_get_thread_num(), "----" !$omp end sections !$omp end parallel ! Ausgabe (ifort): ! 0 20 ! 0 --- ! 1 30 ! 1 ---- end program bsp |
Weitere Direktiven
[Bearbeiten]workshare
single
Kombinierte Direktiven
[Bearbeiten]Unmittelbar aufeinanderfolgende Einzeldirektiven können auch in einer einzigen Direktive zusammengefasst werden. Möglich sind
parallel do
parallel sections
parallel workshare
Beispiel:
Fortran 90/95-Code (free source form) |
program bsp use omp_lib implicit none integer :: i, tnr call omp_set_num_threads( 3 ) !$omp parallel do private(i) schedule(static, 3) do i = 1, 20 tnr = omp_get_thread_num() ! Aktuelle Threadnummer write( *, * ) 'Thread', tnr, ':', i end do !$omp end parallel do end program bsp |
Eine zusammengehörende OpenMP-Direktive darf auch auf mehrere Zeilen verteilt werden.
Beispiel:
Fortran 90/95-Code (free source form) |
program bsp use omp_lib implicit none integer :: i, tnr call omp_set_num_threads( 3 ) !$omp parallel do & !$omp private(i) & !$omp schedule(static, 3) do i = 1, 20 tnr = omp_get_thread_num() ! Aktuelle Threadnummer write( *, * ) 'Thread', tnr, ':', i end do !$omp end parallel do end program bsp |
Synchronisation
[Bearbeiten]Bei der parallelen Programmierung können Situationen auftreten, die bei einer seriellen Programmausführung nie passieren würden, z.B. race conditions. Damit es nicht soweit kommt, bietet OpenMP einige Direktiven zur Synchronisation der Threadausführung.
master
-Direktive
[Bearbeiten]!$omp master ... !$omp end master
Der eingeschlossene Programmblock wird nur vom Master Thread ausgeführt und von den anderen Threads ignoriert.
critical
-Direktive
[Bearbeiten]!$omp critical ... !$omp end critical
Dieser Programmteil wird zwar von allen Threads ausgeführt, allerdings ist sichergestellt, dass dies nicht gleichzeitig erfolgt.
atomic
-Direktive
[Bearbeiten]!$omp atomic
Ähnlich zu critical
. Allerding gilt dies Direktive nur für eine einzelne unmittelbar nachfolgende spezielle Programmanweisung.
barrier
-Direktive
[Bearbeiten]!$omp barrier
Sobald ein Thread eine solche Barriere erreicht, wartet er bis alle andere Threads diese Barriere auch erreicht haben. Erst dann geht's weiter.
flush
-Direktive
[Bearbeiten]!$omp flush
Erstellung eines konsistenten Speicherbildes.
ordered
-Direktive
[Bearbeiten]!$omp do ordered do ... ... !$omp ordered ... !$omp end ordered ... end do !$omp end do
"Geordnete Ausführung" von do
-Schleifen in der gleichen Reihenfolge einer seriellen Abarbeitung.
Beispiel:
Fortran 90/95-Code (free source form) |
program bsp use omp_lib implicit none integer :: i, tnr call omp_set_num_threads( 3 ) !$omp parallel private( i ) !$omp do ordered schedule(static, 3) do i = 1, 20 !$omp ordered tnr = omp_get_thread_num() ! Aktuelle Threadnummer write( *, * ) 'Thread', tnr, ':', i !$omp end ordered end do !$omp end do !$omp end parallel end program bsp |
Ausgabe:
Thread 0 : 1 Thread 0 : 2 Thread 0 : 3 Thread 1 : 4 Thread 1 : 5 Thread 1 : 6 Thread 2 : 7 Thread 2 : 8 Thread 2 : 9 Thread 0 : 10 Thread 0 : 11 Thread 0 : 12 Thread 1 : 13 Thread 1 : 14 Thread 1 : 15 Thread 2 : 16 Thread 2 : 17 Thread 2 : 18 Thread 0 : 19 Thread 0 : 20
Das Modul omp_lib
[Bearbeiten]Das Modul omp_lib
enthält die interface
für die Routinen von OpenMP. Eine mögliche Form des Modules ist nachfolgend abgebildet. In dem Modul wird die import
-Anweisung verwendet, die Teil des Standards Fortran 2003 ist.
Fortran 90/95-Code (free source form) |
module omp_lib ! ! OpenMP Fortran API v2.5 ! implicit none integer, parameter, private :: sgl = kind( 0.0 ) integer, parameter, private :: dbl = kind( 0.0d0 ) integer, parameter, private :: omp_real_kind = dbl integer, parameter, private :: omp_integer_kind = sgl integer, parameter, private :: omp_logical_kind = sgl integer, parameter, private :: omp_lock_kind = dbl integer, parameter, private :: omp_nest_lock_kind = dbl interface subroutine omp_destroy_lock ( var ) import :: omp_lock_kind integer ( kind=omp_lock_kind ), intent(inout) :: var end subroutine omp_destroy_lock subroutine omp_destroy_nest_lock ( var ) import :: omp_nest_lock_kind integer ( kind=omp_nest_lock_kind ), intent(inout) :: var end subroutine omp_destroy_nest_lock function omp_get_dynamic () import :: omp_logical_kind logical ( kind=omp_logical_kind ) :: omp_get_dynamic end function omp_get_dynamic function omp_get_max_threads () import :: omp_integer_kind integer ( kind=omp_integer_kind ) :: omp_get_max_threads end function omp_get_max_threads function omp_get_nested () import :: omp_logical_kind logical ( kind=omp_logical_kind ) :: omp_get_nested end function omp_get_nested function omp_get_num_procs () import :: omp_integer_kind integer ( kind=omp_integer_kind ) :: omp_get_num_procs end function omp_get_num_procs function omp_get_num_threads () import :: omp_integer_kind integer ( kind=omp_integer_kind ) :: omp_get_num_threads end function omp_get_num_threads function omp_get_thread_num () import :: omp_integer_kind integer ( kind=omp_integer_kind ) :: omp_get_thread_num end function omp_get_thread_num function omp_get_wtick () import :: omp_real_kind real ( kind=omp_real_kind ) :: omp_get_wtick end function omp_get_wtick function omp_get_wtime () import :: omp_real_kind real ( kind=omp_real_kind ) :: omp_get_wtime end function omp_get_wtime subroutine omp_init_lock ( var ) import :: omp_lock_kind integer ( kind=omp_lock_kind ), intent(out) :: var end subroutine omp_init_lock subroutine omp_init_nest_lock ( var ) import :: omp_nest_lock_kind integer ( kind=omp_nest_lock_kind ), intent(out) :: var end subroutine omp_init_nest_lock function omp_in_parallel () import :: omp_logical_kind logical ( kind=omp_logical_kind ) :: omp_in_parallel end function omp_in_parallel subroutine omp_set_dynamic ( enable_expr ) import :: omp_logical_kind logical ( kind=omp_logical_kind ), intent(in) :: enable_expr end subroutine omp_set_dynamic subroutine omp_set_lock ( var ) import :: omp_lock_kind integer ( kind=omp_lock_kind ), intent(inout) :: var end subroutine omp_set_lock subroutine omp_set_nest_lock ( var ) import :: omp_nest_lock_kind integer ( kind=omp_nest_lock_kind ), intent(inout) :: var end subroutine omp_set_nest_lock subroutine omp_set_nested ( enable_expr ) import :: omp_logical_kind logical ( kind=omp_logical_kind ), intent(in) :: enable_expr end subroutine omp_set_nested subroutine omp_set_num_threads ( number_of_threads_expr ) import :: omp_integer_kind integer ( kind=omp_integer_kind ), intent(in) :: number_of_threads_expr end subroutine omp_set_num_threads function omp_test_lock ( var ) import :: omp_logical_kind, omp_lock_kind logical ( kind=omp_logical_kind ) :: omp_test_lock integer ( kind=omp_lock_kind ), intent(inout) :: var end function omp_test_lock function omp_test_nest_lock ( var ) import :: omp_integer_kind, omp_nest_lock_kind integer ( kind=omp_integer_kind ) :: omp_test_nest_lock integer ( kind=omp_nest_lock_kind ), intent(inout) :: var end function omp_test_nest_lock subroutine omp_unset_lock ( var ) import :: omp_lock_kind integer ( kind=omp_lock_kind ), intent(inout) :: var end subroutine omp_unset_lock subroutine omp_unset_nest_lock ( var ) import :: omp_nest_lock_kind integer ( kind=omp_nest_lock_kind ), intent(inout) :: var end subroutine omp_unset_nest_lock end interface end module omp_lib |
Literatur
[Bearbeiten]- Rohit Chandra, Leonardo Dagum, Dave Kohr, Dror Maydan, Jeff McDonald, Ramesh Menon, Parallel Programming in OpenMP, Morgan Kaufmann Publishers, 2001, ISBN-13: 978-1-55860-671-5, ISBN-10: 1-55860-671-8
Weblinks
[Bearbeiten]<< zur Fortran-Startseite | |
< LAPACK | Fortran und Tcl/Tk > |