Россия, г. Санкт-Петербург |
Основные конструкции OpenMP
Особенности реализации директив OpenMP
В этом разделе рассмотрим некоторые специфические особенности реализации директив OpenMP.
Количество потоков в параллельной программе определяется либо значением переменной окружения OMP_NUM_THREADS, либо специальными функциями, вызываемыми внутри самой программы. Задание переменной окружения OMP_NUM_THREADS в операционной системе Linux осуществляется следующим образом с помощью команды
export OMP_NUM_THREADS=256
Здесь было задано 256 параллельных потоков.
Просмотреть состояние переменной окружения OMP_NUM_THREADS можно, например, с помощью следующей команды:
export | grep OMP_NUM_THREADS
Каждый поток имеет свой номер thread_number, начинающийся от нуля (для главного потока) и заканчивающийся OMP_NUM_THREADS-1. Определить номер текущего потока можно с помощью функции omp_get_thread_num().
Каждый поток выполняет структурный блок программы, включенный в параллельный регион. В общем случае синхронизации между потоками нет, и заранее предсказать завершение потока нельзя. Управление синхронизацией потоков и данных осуществляется программистом внутри программы. После завершения выполнения параллельного структурного блока программы все параллельные потоки за исключением главного прекращают свое существование.
Пример ветвления во фрагменте программы, написанной на языке C/C++, в зависимости от номера параллельного потока показан в примере 2.8.
#pragma omp parallel { myid = omp_get_thread_num ( ) ; if (myid == 0) do_something ( ) ; else do_something_else (myid) ; }2.8. Пример ветвления в программе в зависимости от номера параллельного потока
В этом примере в первой строке параллельного блока вызывается функция omp_get_thread_num, возвращающая номер параллельного потока. Этот номер сохраняется в локальной переменной myid.Далее в зависимости от значения переменной myid вызывается либо функция do_something() в первом параллельном потоке с номером 0, либо функция do_something_else(myid) во всех остальных параллельных потоках.
В OpenMP существуют различные режимы выполнения (Execution Mode) параллельных структурных блоков. Возможны следующие режимы:
- Динамический режим (Dynamic Mode). Этот режим установлен по умолчанию и определяется заданием переменной окружения OMP_DYNAMIC в операционной системе Linux. Задать эту переменную можно, например, с помощью следующей команды операционной системы Linux:
export OMP_DYNAMIC
Кроме того, существует возможность задать этот режим и саму переменную внутри программы, вызвав функцию
omp_set_dynamic()
Отметим, что на компьютерах Silicon Graphics S350 и Kraftway G-Scale S350 вторая возможность отсутствует. В динамическом режиме количество потоков определяется самой операционной системой в соответствии со значением переменной окружения OMP_NUM_THREADS.В процессе выполнения параллельной программы при переходе от одной области распараллеливания к другой эта переменная может изменять свое значение;
- Статический режим (Static Mode). Этот режим определяется заданием переменной окружения OMP_STATIC в операционной системе Linux. В этом случае количество потоков определяется программистом. Задать переменную окружения OMP_STATIC можно, например, с помощью следующей команды операционной системы Linux:
export OMP_STATIC
Кроме того, существует возможность задать этот режим и саму переменную внутри программы, вызвав функцию
omp_set_static()
Однако на компьютерах Silicon Graphics S350 и Kraftway G-Scale S350 вторая возможность отсутствует;
- Параллельные структурные блоки могут быть вложенными, но компилятор иногда по ряду причин может выполнять их и последовательно в рамках одного потока. Вложенный режим выполнения параллельных структурных блоков определяется заданием переменной окружения OMP_NESTED=[FALSE|TRUE] (по умолчанию задается FALSE ) в операционной системе Linux. Задать эту переменную можно, например, с помощью следующей команды операционной системы Linux:
setenv OMP_NESTED TRUE
Кроме того, существует возможность задать этот режим и саму переменную внутри программы, вызвав функцию
omp_set_nested( TRUE | FALSE )
Однако на компьютерах Silicon Graphics S350 и Kraftway G-Scale S350 вторая возможность отсутствует.
Директивы OpenMP
Теперь перейдем к подробному рассмотрению директив OpenMP и механизмов их реализации.
Директивы shared, private и default
Эти директивы (предложения OpenMP) используются для описания типов переменных внутри параллельных потоков.
Предложение OpenMP
shared( var1, var2, …, varN )
определяет переменные var1, var2, …, varN как общие переменные для всех потоков. Они размещаются в одной и той же области памяти для всех потоков.
Предложение OpenMP
private( var1, var2, …, varN )
определяет переменные var1, var2, …, varN как локальные переменные для каждого из параллельных потоков. В каждом из потоков эти переменные имеют собственные значения и относятся к различным областям памяти: локальным областям памяти каждого конкретного параллельного потока.
В качестве иллюстрации использования директив OpenMP shared и private рассмотрим фрагмент программы (Пример 2.9). В этом примере переменная a определена как общая и является идентификатором одномерного массива. Переменные myid и x определены как локальные переменные для каждого из параллельных потоков. В каждом из параллельных потоков локальные переменные получают собственные значения. После чего при выполнении условия x<1.0 значение локальной переменной x присваивается myid -й компоненте общего для всех потоков массива a. Значение x будет неопределенным, если не определить x как переменную типа private. Отметим, что значения private -переменных не определены до и после блока параллельных вычислений.
c$omp parallel shared (a) c$omp& private (myid, x) myid = omp_get_thread_num ( ) x = work (myid) if (x < 1.0) then a (myid) = x endif2.9. Пример использования директив OpenMP shared и private в параллельной области программы
Предложение OpenMP
default ( shared | private | none )
задает тип всех переменных, определяемых по умолчанию в последующем параллельном структурном блоке как shared, private или none. Например, если во фрагменте программы (примере 2.9) вместо
private( myid, x )
написать
default( private )
то определяемые далее по умолчанию переменные myid и x будут автоматически определены как private.
Директивы firstprivate и lastprivate
Директивы (предложения) OpenMP firstprivate и lastprivate используются для описания локальных переменных, инициализируемых внутри параллельных потоков. Переменные, описанные как firstprivate, получают свои значения из последовательной части программы. Переменные, описанные как lastprivate, сохраняют свои значения при выходе из параллельных потоков при условии их последовательного выполнения.
Предложение OpenMP
firstprivate( var1, var2, ..., varN )
определяет переменные var1, var2, ..., varN как локальные переменные для каждого из параллельных потоков, причем инициализация значений этих переменных происходит в самом начале параллельного структурного блока по значениям из предшествующего последовательного структурного блока программы.
В качестве иллюстрации рассмотрим фрагмент программы (Пример 2.10). В этом примере в каждом параллельном потоке используется своя переменная c, но значение этой переменной перед входом в параллельный блок программы берется из предшествующего последовательного блока.
program first integer :: myid, c integer, external :: omp_get_thread_num c=98 !$omp parallel private (myid) !$omp& firstprivate (c) myid = omp_get_thread_num ( ) write (6, *) 'T: ', myid, 'c=', c !$omp end parallel end program first2.10. Пример использования директивы OpenMP firstprivate в параллельной области программы
T:1 с=98 T:3 с=98 T:2 с=98 T:0 с=98
Предложение OpenMP
lastprivate( var1, var2, ..., varN )
определяет переменные var1, var2, ..., varN как локальные переменные для каждого из параллельных потоков, причем значения этих переменных сохраняются при выходе из параллельного структурного блока при условии, что параллельные потоки выполнялись в последовательном порядке.
В качестве иллюстрации рассмотрим фрагмент программы (Пример 2.11). В этом примере локальная переменная i принимает различные значения в каждом потоке параллельного структурного блока. После завершения параллельного структурного блока переменная i сохраняет свое последнее значение, полученное в последнем параллельном потоке, при условии последовательного выполнения всех параллельных потоков, т. е. n=N+1.
c$omp do shared ( x ) c$omp& lastprivate ( i) do i = 1, N x (i) = a enddo n = i2.11. Пример использования директивы OpenMP lastprivate в параллельной области программы