Zum Inhalt springen

Fortran: OpenMP

Aus Wikibooks
<< zur Fortran-Startseite
< LAPACK Fortran und Tcl/Tk >



Wikipedia hat einen Artikel zum Thema:


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 Option num_threads() festgelegt.
  • Verwendete OpenMP-Funktionen:
    • omp_get_thread_num() ... Aktuelle Thread-Nummer
    • omp_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 Option num_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 wie private. Der Unterschied zu private 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
[Bearbeiten]

<< zur Fortran-Startseite
< LAPACK Fortran und Tcl/Tk >