Дополнительные возможности OpenMP
Задание переменных окружения с помощью функций runtime OpenMP
В предыдущих лекциях уже затрагивались вопросы задания переменных окружения, определяющих режимы работы параллельных частей программ. Было показано, что для задания их значений можно воспользоваться командами операционной системы Linux или директивами OpenMP. Однако в OpenMP существует еще одна возможность задания переменных окружения: с помощью функций библиотеки runtime OpenMP. Обращение к этим функциям осуществляется непосредственно в программе и ничем не отличается от вызова обычных функций. Однако следует иметь в виду, что подобная возможность существует не во всех реализациях OpenMP. Так, в реализациях OpenMP, поддерживаемых компанией Silicon Graphics, такая возможность отсутствует. Это же замечание относится и к реализациям OpenMP на многопроцессорных вычислительных системах G-Scale s-350 компании Kraftway.
В программах, написанных на C/C++ с использованием OpenMP, существуют следующие возможности задания переменных окружения с помощью средств библиотеки реального времени runtime OpenMP.
С помощью вызова функции
(void) omp_set_num_threads(int num_threads)
можно задать число потоков в области параллельных вычислений, т. е. определить значение переменной окружения OMP_NUM_THREADS. При завершении работы эта функция не возвращает в программу никаких значений.
int omp_get_num_threads()
напротив, возвращает в программу целочисленное значение, равное количеству параллельных потоков в текущий момент времени.
int omp_get_max_threads()
возвращает в программу целочисленное значение, равное максимальному количеству параллельных потоков, которое может быть возвращено функцией omp_get_num_threads.
Для определения номера параллельного потока в текущий момент времени программы можно воспользоваться функцией
int_omp_get_thread_num()
Она возвращает целочисленное значение в диапазоне от 0 до OMP_NUM_THREADS-1.
Определить количество процессоров, доступных программе в текущей точке, можно с помощью функции
int omp_get_num_procs()
Следующая функция позволяет идентифицировать, в какой области программы (параллельной или последовательной) в текущий момент времени проводятся вычисления
(int/logical) omp_in_parallel()
Она возвращает значение TRUE или 1 в точке параллельной области программы и FALSE или 0 в точке последовательной области программы.
Задать или отменить динамический режим работы программы можно, воспользовавшись функцией
(void) omp_set_dynamic( TRUE | FALSE )
Для задания динамического режима следует использовать параметр TRUE, а для его отмены - параметр FALSE. При этом в списке переменных окружения определяется или удаляется переменная окружения OMP_DYNAMIC или задается ее значение, соответственно равное TRUE или FALSE.
Следующая функция позволяет идентифицировать, какой режим работы параллельной части программы (динамический или статический) осуществляется в текущий момент времени при вызове функции
(int/logical) omp_get_dynamic()
Эта функция возвращает значение TRUE или 1, если режим динамический, и FALSE или 0 в случае статического режима.
Задать или отменить вложенный режим параллельной обработки процессов в параллельной области программы можно с помощью функции
(void) omp_set_nested( TRUE | FALSE )
Она устанавливает или отменяет вложенный режим параллельной обработки. При этом в списке переменных окружения определяется или удаляется переменная окружения OMP_NESTED либо задается ее значение, соответственно равное TRUE или FALSE.
Следующая функция позволяет идентифицировать, установлен или нет вложенный режим параллельной обработки в момент вызова функции
(int/logical) omp_get_nested()
Эта функция возвращает значение TRUE или 1 для вложенного режима параллельной обработки и значение FALSE или 0 при отсутствии такового.
При обращении ко всем вышеперечисленным функциям в заголовки программ на языке С/С++ необходимо включать строку:
#include <omp.h>
В программах, написанных на языке Fortran, существуют одноименные аналогичные функции. При этом функции, возвращающие значения, реализованы в виде функций, а функции, устанавливающие значения переменных окружения и не возвращающие никаких значений, реализованы в виде подпрограмм. Тип функций в программах, написанных на языке Fortran, должен быть определен в соответствии с типом возвращаемых данных.
Передача данных с помощью директивы threadprivate
Проблема передачи данных в параллельных программах, написанных с использованием OpenMP, уже рассматривалась в предыдущих разделах. Однако был изложен лишь вариант передачи (миграции) данных из главного потока программы в параллельные. В этом же разделе мы остановимся на проблеме передачи данных между параллельными потоками из одного параллельного структурного блока программы в другой, минуя промежуточный последовательный структурный блок. Для реализации такого механизма передачи данных в OpenMP имеется специальная директива threadprivate.
Директива threadprivate применяется к глобальным переменным программы, которые нужно сделать локальными переменными для каждого из параллельных потоков. Кроме того, эта директива позволяет передавать локальные данные из одного параллельного структурного блока в другой, а также сохранять локальные данные из параллельных потоков на протяжении всей программы. В программах, написанных на языке Fortran, для этого необходимо поместить переменные в common-блок, а затем описать этот блок с помощью директивы threadprivate следующим образом:
c$omp threadprivate (/cb1/, [/cb2/ [ , ..., /cbN/]]
Здесь cb1, cb2, ..., cbN - имена common -блоков, для которых создаются локальные копии во всех параллельных потоках. В результате с первой же параллельной области, следующей за директивой threadprivate, будут созданы локальные копии common -блоков. Важно отметить, что в процессе передачи данных сохраняется соответствие между значениями передаваемых переменных и номерами параллельных потоков.
Пример фрагмента программы, написанной на языке Fortran с использованием директивы OpenMP threadprivate, приведен в примере 4.1.
Как видно из этого примера, для передачи данных из одного параллельного структурного блока в другой в данном примере используется переменная x, определенная в common-блоке mine. Имя этого common-блока описано с помощью директивы threadprivate. В первом параллельном структурном блоке программы вычисляются и выводятся на печать значения переменной x в каждом из четырех параллельных потоков. После завершения первого параллельного блока значения этой переменной выводятся в следующем последовательном блоке программы. Отметим, что в этом случае напечатанное значение соответствует главному потоку программы (его индекс tid всегда равен 0 ), в который передает свое значение x первый параллельный поток с индексом tid, равным 0.
Во втором параллельном структурном блоке значения переменной x не изменяются и выводятся на печать в точном соответствии с индексами параллельных потоков tid, установленных в первом параллельном блоке с индексом tid=0.
В программах, написанных на языке Fortran, предложение threadprivate имеет следующий вид:
program region integer, external :: omp_get_thread_num integer :: tid, x common/mine/ x !$omp threadprivate (/mine/) call omp_set_num_threads (4) !$omp parallel private (tid) tid = omp_get__thread_num ( ) x = tid * 10 + 1 print *,"T:", tid," inside first parallel region x =",x !$omp end parallel print *,"T:", tid," outside parallel region x =",x !$omp parallel private (tid) tid = omp_get_thread_num( ) print *,"T:", tid," inside next parallel region x =",x !$omp end parallel end program region4.1. Фрагмент программы на языке Fortran с использованием директивы OpenMP threadprivate
Результаты работы программы:
T: 0 inside first parallel region x = 1 T: 1 inside first parallel region x = 11 T: 2 inside first parallel region x = 21 T: 3 inside first parallel region x = 31 T: 0 outside parallel region x = 1 T: 0 inside next parallel region x = 1 T: 2 inside next parallel region x = 21 T: 3 inside next parallel region x = 31 T: 1 inside next parallel region x = 11
Здесь cb1, cb2, ..., cbN - список переменных, для которых создаются локальные копии во всех параллельных потоках.
Отметим, что элементами списка не могут быть ссылки или переменные, не полностью определенные. Кроме того, адреса констант также недопустимы в списке.
Все переменные, включенные в список директив threadprivate, должны быть проинициализированы до начала первого параллельного блока, и их инициализация возможна только один раз на протяжении всей программы. Кроме того, переменные, включенные в список, не могут фигурировать в других предложениях OpenMP, за исключением copyin, schedule или if. Категорически запрещено их использование в директивах private, firstprivate, lastprivate, shared и reduction.
Не допускается применение передачи значений переменных из одного параллельного структурного блока в другой в динамическом ( dynamic ) режиме работы параллельной программы. На всем протяжении действия директивы threadprivate должен быть установлен статический режим работы параллельной программы.
В примере 4.2 приведен пример фрагмента параллельной программы, написанной на языке C/C++ с использованием директивы OpenMP threadprivate. В этом примере для отмены динамического режима явно вызывается функция из библиотеки реального времени runtime OpenMP omp_set_dynamic с параметром 0.
#include <omp.h> int alpha [10], beta[10], i #pragma omp threadprivate (alpha) main ( ) { /*Выключение динамического режима*/ omp_set_dynamic (0); /*1-й параллельный блок */ #pragma omp parallel private (i, beta) for (i=0; i<10; i++) alpha[i] = beta[i] = i; /*2-й параллельный блок*/ #pragma omp parallel printf ("alpha[3]=%d and beta [3] =%d\n", alpha[3], beta[3]); }4.2. Фрагмент параллельной программы на языке C/C++ с использованием директивы OpenMP threadprivate