Загрузка и синхронизация в OpenMP
Синхронизация типа master
Синхронизация типа master используется для определения структурного блока программы, который будет выполняться исключительно в главном потоке (параллельном потоке с нулевым номером) из всего набора параллельных потоков. Этот структурный блок следует за директивой
#pragma omp master
в программах, написанных на языке C/C++. В программах на языке Fortran синхронизация типа master устанавливается следующим образом:
c$omp master <структурный блок> c$omp end master
В примере 3.4 приведен пример фрагмента программы, иллюстрирующий использование синхронизации типа master. В этом примере операторы print и read выполняются исключительно в главном потоке.
! $omp parallel shared (c, scale)
! $omp& private (j, myid)
myid = omp_get_thread_num ( )
! $omp master
print *, 'T: ', myid, ' enter scale'
read *, scale
! $omp end master
! $omp barrier
! $omp do
do j = 1, N
c (j) = scale * c (j)
enddo
! $omp end do
! $omp end parallel
3.4.
Пример синхронизации типа master
Синхронизация типа ordered
Синхронизация типа ordered используется для определения потоков в параллельной области программы, которые выполняются в порядке, соответствующем последовательной версии программы.
с$omp parallel default (shared) private (I, J)
с$omp do ordered
do I = 1, N
do J = 1, M
Z(I) = Z(I) + X (I, J) * Y(J, I)
enddo
с$omp ordered
if (I<21) then
print*, 'Z (',I,') = ', Z (I)
endif
с$omp end ordered
enddo
3.5.
Пример программы с синхронизацией типа ordered
Результаты работы программы:
Z (1) = 1007.167786 Z (2) = 1032.933350 Z (3) = 1033.125610 Z (4) = 1009.944641 Z (5) = 1016.547302 Z (6) = 1005.789124 Z (7) = 1025.048584 Z (8) = 1003.904358 Z (9) = 995.5405273 Z (10) = 991.2892456 Z (11) = 1011.334167 Z (12) = 1010.631897 Z (13) = 1009.581848 Z (14) = 976.2397461 Z (15) = 978.1119385 Z (16) = 977.7111816 Z (17) = 971.2011719 Z (18) = 998.4275513 Z (19) = 1018.487000 Z (20) = 978.0640259
В программах на языке C/C++ синхронизация типа ordered описывается следующим образом:
#pragma omp ordered <структурный блок>
а в программах, написанных на языке Fortran, синхронизация типа ordered устанавливается так:
c$omp ordered <структурный блок> c$omp end ordered
В примере 3.5 приведен пример фрагмента программы, иллюстрирующий применение синхронизации типа ordered.
Видно, что в приведенном примере вывод результатов элементов массива Z осуществляется в порядке возрастания индексов элементов массива, как в обычной последовательной программе.
Синхронизация типа flush
Синхронизация типа flush используется для обновления значений локальных переменных, перечисленных в качестве аргументов этой команды, в оперативной памяти. После выполнения этой директивы все переменные, перечисленные в этой директиве, имеют одно и то же значение для всех параллельных потоков.
В программах на языке C/C++ синхронизация типа flush описывается следующим образом:
#pragma omp flush(var1, [var2, [..., varN ]])
На языке Fortran эта директива синхронизации типа flush выглядит так:
c$omp flush var1, [var2, [..., varN ]])
Здесь var1, var2, ..., varN - список переменных, значения которых сохраняются в оперативной памяти в момент выполнения директории flush.
Пример фрагмента программы, иллюстрирующий использование синхронизации типа flush, приведен в примере 3.6. В этом примере значения переменной done записываются в оперативную память для того, чтобы все другие процессы имели доступ к актуальным значениям этой переменной.
program flush
integer, parameter :: M=1600000
integer, dimension (M) :: c
integer :: stop, sum, tid
integer, dimension (0:1) :: done
integer, external :: omp_get_thread_num
call omp_set_num_threads (2)
c = 1
c (345) = 9
!$omp parallel default (private) shared (done, c, stop)
tid=omp_get_ thread_num ( )
done (tid) = 0
if (tid == 0) then
neigh = 1
else
neigh = 0
end if
!$omp barrier
if (tid == 0) then
do j = 1, M
if (c j) == 9) stop = j
enddo
endif
done (tid) = 1
!$omp flush (done)
do while (done(neigh) .eq. 0)
!$omp flush (done)
enddo
if (tid == 1) then
sum=0
do j = 1, stop - 1
sum = sum + c (j)
enddo
endif
!$omp end parallel
end program flush
3.6.
Пример синхронизации типа flush
Загрузка процессов в OpenMP. Директива schedule
Проблема загрузки параллельных потоков является важной проблемой не только для параллельного программирования с использованием OpenMP, но и для всего параллельного программирования в целом. Эта проблема тесно связана с проблемой балансировки загрузки процессоров параллельных высокопроизводительных вычислительных систем, а также с проблемой повышения эффективности работы параллельных программ. Понятно, что для высокопроизводительной параллельной вычислительной системы успешное решение проблемы балансировки загрузки процессоров является ключом к решению задачи повышения эффективности вычислительной системы в целом. Как известно, процессы по-разному могут использовать вычислительные возможности процессоров. Так, на стадии интенсивных арифметических вычислений коэффициент загрузки процессоров обычно близок к 100%. На стадии интенсивных операций ввода/вывода коэффициент загрузки процессоров меняется в диапазоне от нескольких процентов до десятков процентов. Кроме того, процессоры могут простаивать и в том случа е, когда процессы, переданные им, уже обработаны, а другие процессоры все еще продолжают обрабатывать процессы той же задачи. Для исключения простоев или уменьшения времени простоев применяют различные методы балансировки процессов. Существующие в OpenMP различные методы загрузки процессов также могут быть применены для улучшения балансировки работы параллельных вычислительных систем.
Для распределения работы между процессами в OpenMP имеется директива schedule с параметрами, позволяющими задавать различные режимы загрузки процессоров. Ниже приведен общий вид предложения schedule в OpenMP.
schedule( type [ , chunk ] )
Здесь type - параметр, определяющий тип загрузки, а chunk - параметр, который определяет порции данных, пересылаемых между процессами (по умолчанию значение параметра chunk равно 1 ).
В OpenMP параметр type принимает одно из следующих значений:
- static,
- dynamic,
- guided,
- runtime.
Загрузка типа static
В этом случае вся совокупность загружаемых процессов разбивается на равные порции размера chunk, и эти порции последовательно распределяются между процессорами (или потоками, которые затем и выполняются на этих процессорах) с первого до последнего и т. д.
В качестве примера использования загрузки типа static рассмотрим фрагмент программы на языке Fortran, приведенный в примере 3.7.
с$omp do shared (x) private (i)
с$omp& schedule (static, 1000)
do i = 1, 12000
... work ...
enddo
3.7.
Пример загрузки schedule(static, chunk=1000)
Схема распределения загрузки процессов по процессорам (или потокам ( threads ), которые затем выполняются на процессорах), соответствующая этому примеру, приведена на рис.3.1. Как видно из этой схемы, все 12000 процессов разбиты на 12 порций по 1000 процессов ( chunk=1000 ). Загрузка порций в потоки ( threads ) происходит последовательно. Сначала порции в порядке нумерации загружаются в первый, второй, третий и четвертый потоки, а затем загрузка производится опять в том же порядке, начиная с первого потока и т. д.
Загрузка типа dynamic
В этом случае вся совокупность загружаемых процессов, как и в предыдущем варианте, разбивается на равные порции размера chunk, но эти порции загружаются последовательно в освободившиеся потоки (процессоры).
Пример применения загрузки типа dynamic приведен в следующем фрагменте программы (пример 3.8).
с$omp do shared (x) private (i)
с$omp& schedule (dynamic, 1000)
do i = 1, 10000
... work ...
enddo
3.8.
Пример загрузки schedule(dynamic, chunk=1000)
Загрузка типа guided
В этом случае вся совокупность загружаемых процессов разбивается на порции, размер которых определяется операционной системой динамически и не превышает размер chunk. Загрузка порций происходит, как и в динамическом режиме, в первый освободившийся поток, затем следующий освободившийся поток и т. д.
Пример применения загрузки типа guided приведен в следующем фрагменте программы, изображенном на пример 3.9.
с$omp do shared (x) private (i)
с$omp& schedule (guided, 55)
do i = 1, 12000
... work ...
enddo
3.9.
Пример загрузки schedule(guided, chunk=55)
В этом режиме обеспечивается достаточно сбалансированная загрузка потоков с небольшими задержками при завершении параллельной обработки.
Загрузка типа runtime
В этом случае загрузка порций процессов по потокам (или процессорам) определяется значением переменной окружения OMP_SCHEDULE. Значение этой переменной проверяется перед каждой загрузкой процессов в потоки во время работы программы. По умолчанию оно static.
Два примера задания режимов загрузки типа runtime с помощью команд операционной системы Linux приведены ниже.
$ setenv OMP_SCHEDULE static,1000 $ setenv OMP_SCHEDULE dynamic
В первом примере через значение переменной окружения в качестве загрузки runtime задается режим static с размером порции равным 1000. Во втором же примере в качестве загрузки runtime задается режим dynamic с размером порции, по умолчанию заданным равным 1.
