Опубликован: 15.04.2008 | Уровень: специалист | Доступ: платный
Лекция 3:

Загрузка и синхронизация в OpenMP

< Лекция 2 || Лекция 3: 12 || Лекция 4 >

Синхронизация типа 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 ) происходит последовательно. Сначала порции в порядке нумерации загружаются в первый, второй, третий и четвертый потоки, а затем загрузка производится опять в том же порядке, начиная с первого потока и т. д.

Схема загрузки процессоров для программы, приведенной в примере 3.7

Рис. 3.1. Схема загрузки процессоров для программы, приведенной в примере 3.7

Загрузка типа 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.

< Лекция 2 || Лекция 3: 12 || Лекция 4 >
Алексей Ищенко
Алексей Ищенко
Россия, г. Санкт-Петербург
Виолета Белянина
Виолета Белянина
Россия