С. антоновпараллельноепрограммированиесиспользованиемтехнологииopenMP
Скачать 0.55 Mb.
|
shared ). Исключение составляют переменные, являющиеся счетчиками итераций в цикле, по очевидным при- чинам. Переменные, порождённые внутри параллельной области, по умолча- нию являются локальными ( private ). Явно назначить класс переменных по умолчанию можно с помощью опции default . Не рекомендуется постоянно полагаться на правила по умолчанию, для большей надёжности лучше всегда явно описывать классы используемых переменных, указывая в директивах OpenMP опции private , shared , firstprivate , lastprivate , reduction Пример 12 демонстрирует использование опции private . В данном примере переменная n объявлена как локальная переменная в параллельной области. Это значит, что каждая нить будет работать со своей копией переменной n , при этом в начале параллельной области на каждой нити переменная n не бу- дет инициализирована. В ходе выполнения программы значение переменной n будет выведено в четырёх разных местах. Первый раз значение n будет вы- ведено в последовательной области, сразу после присваивания переменной n 30 значения 1 . Второй раз все нити выведут значение своей копии переменной n в начале параллельной области, неинициализированное значение может за- висеть от реализации. Далее все нити выведут свой порядковый номер, полу- ченный с помощью функции omp_get_thread_num() и присвоенный пере- менной n . После завершения параллельной области будет ещё раз выведено значение переменной n , которое окажется равным 1 (не изменилось во время выполнения параллельной области). #include #include int main(int argc, char *argv[]) { int n=1; printf("n в последовательной области ( начало ): %d\n", n); #pragma omp parallel private(n) { printf(" Значение n на нити ( на входе ): %d\n", n); /* Присвоим переменной n номер текущей нити */ n=omp_get_thread_num(); printf(" Значение n на нити ( на выходе ): %d\n", n); } printf("n в последовательной области ( конец ): %d\n", n); } Пример 12a. Опция private наязыкеСи. program example12b include "omp_lib.h" integer n n=1 print *, "n в последовательной области ( начало ): ", n !$omp parallel private(n) print *, " Значение n на нити ( на входе ): ", n C Присвоим переменной n номер текущей нити n=omp_get_thread_num() print *, " Значение n на нити ( на выходе ): ", n !$omp end parallel print *, "n в последовательной области ( конец ): ", n end Пример 12b. Опция private наязыкеФортран. Пример 13 демонстрирует использование опции shared . Массив m объявлен общим для всех нитей. В начале последовательной области массив m запол- няется нулями и выводится на печать. В параллельной области каждая нить находит элемент, номер которого совпадает с порядковым номером нити в общем массиве, и присваивает этому элементу значение 1 . Далее, в последо- вательной области печатается изменённый массив m 31 #include #include int main(int argc, char *argv[]) { int i, m[10]; printf(" Массив m в начале :\n"); /* Заполним массив m нулями и напечатаем его */ for (i=0; i<10; i++){ m[i]=0; printf("%d\n", m[i]); } #pragma omp parallel shared(m) { /* Присвоим 1 элементу массива m, номер которого совпадает с номером текущий нити */ m[omp_get_thread_num()]=1; } /* Ещё раз напечатаем массив */ printf(" Массив m в конце :\n"); for (i=0; i<10; i++) printf("%d\n", m[i]); } Пример 13a. Опция shared наязыкеСи. program example13b include "omp_lib.h" integer i, m(10) print *, " Массив m в начале :" C Заполним массив m нулями и напечатаем его do i=1, 10 m(i)=0 print *, m(i) end do !$omp parallel shared(m) C Присвоим 1 элементу массива m, номер которого C совпадает с номером текущий нити m(omp_get_thread_num()+1)=1 !$omp end parallel C Ещё раз напечатаем массив */ print *, " Массив m в конце :" do i=1, 10 print *, m(i) end do end Пример 13b. Опция shared наязыкеФортран. В языке Си статические ( static ) переменные, опредёленные в параллельной области программы, являются общими ( shared ). Динамически выделенная память также является общей, однако указатель на неё может быть как об- щим, так и локальным. 32 В языке Фортран по умолчанию общими ( shared ) являются элементы COMMON-блоков. Отдельные правила определяют назначение классов переменных при входе и выходе из параллельной области или параллельного цикла при использова- нии опций reduction , firstprivate , lastprivate , copyin Пример 14 демонстрирует использование опции firstprivate . Переменная n объявлена как firstprivate в параллельной области. Значение n будет выведено в четырёх разных местах. Первый раз значение n будет выведено в последовательной области сразу после инициализации. Второй раз все нити выведут значение своей копии переменной n в начале параллельной области, и это значение будет равно 1 Далее, с помощью функции omp_get_thread_num() все нити присвоят переменной n свой порядковый номер и ещё раз выведут значение n . В последовательной области будет ещё раз выведено значение n , которое снова окажется равным 1 #include #include int main(int argc, char *argv[]) { int n=1; printf(" Значение n в начале : %d\n", n); #pragma omp parallel firstprivate(n) { printf(" Значение n на нити ( на входе ): %d\n", n); /* Присвоим переменной n номер текущей нити */ n=omp_get_thread_num(); printf(" Значение n на нити ( на выходе ): %d\n", n); } printf(" Значение n в конце : %d\n", n); } Пример 14a. Опция firstprivate наязыкеСи. program example14b include "omp_lib.h" integer n n=1 print *, " Значение n в начале : ", n !$omp parallel firstprivate(n) print *, " Значение n на нити ( на входе ): ", n C Присвоим переменной n номер текущей нити n=omp_get_thread_num() print *, " Значение n на нити ( на выходе ): ", n !$omp end parallel print *, " Значение n в конце : ", n end Пример 14b. Опция firstprivate наязыкеФортран. 33 Директива threadprivate указывает, что переменные из списка должны быть размножены с тем, чтобы каждая нить имела свою локальную копию. Си: #pragma omp threadprivate( список ) Фортран: !$omp threadprivate( список ) Директива threadprivate может позволить сделать локальные копии для статических переменных языка Си и COMMON-блоков языка Фортран, кото- рые по умолчанию являются общими. Для корректного использования ло- кальных копий глобальных объектов нужно гарантировать, что они исполь- зуются в разных частях программы одними и теми же нитями. Если на локальные копии ссылаются в разных параллельных областях, то для сохра- нения их значений необходимо, чтобы не было объемлющих параллельных областей, количество нитей в обеих областях совпадало, а переменная OMP_DYNAMIC была установлена в false с начала первой области до начала второй. Переменные, объявленные как threadprivate , не могут использо- ваться в опциях директив OpenMP, кроме copyin , copyprivate , schedule , num_threads , if Пример 15 демонстрирует использование директивы threadprivate . Гло- бальная переменная n объявлена как threadprivate переменная. Значение переменной n выводится в четырёх разных местах. Первый раз все нити вы- ведут значение своей копии переменной n в начале параллельной области, и это значение будет равно 1 на нити-мастере и 0 на остальных нитях. Далее с помощью функции omp_get_thread_num() все нити присвоят переменной n свой порядковый номер и выведут это значение. Затем в последовательной области будет ещё раз выведено значение переменной n , которое окажется равным порядковому номеру нити-мастера, то есть 0 . В последний раз значе- ние переменной n выводится в новой параллельной области, причём значение каждой локальной копии должно сохраниться. 34 #include #include int n; #pragma omp threadprivate(n) int main(int argc, char *argv[]) { int num; n=1; #pragma omp parallel private (num) { num=omp_get_thread_num(); printf(" Значение n на нити %d ( на входе ): %d\n", num, n); /* Присвоим переменной n номер текущей нити */ n=omp_get_thread_num(); printf(" Значение n на нити %d ( на выходе ): %d\n", num, n); } printf(" Значение n ( середина ): %d\n", n); #pragma omp parallel private (num) { num=omp_get_thread_num(); printf(" Значение n на нити %d ( ещё раз ): %d\n", num, n); } } Пример 15a. Директива threadprivate наязыкеСи. program example15b include "omp_lib.h" common/nnn/n integer n, num; !$omp threadprivate(/nnn/) n=1; !$omp parallel private (num) num=omp_get_thread_num() print *, " Значение n на нити ", num, " ( на входе ): ", n C Присвоим переменной n номер текущей нити n=omp_get_thread_num(); print *, " Значение n на нити ", num, " ( на выходе ): ", n !$omp end parallel print *, " Значение n ( середина ): ", n !$omp parallel private (num) num=omp_get_thread_num() print *, " Значение n на нити ", num, " ( ещё раз ): ", n !$omp end parallel end Пример 15b. Директива threadprivate наязыкеФортран. Если необходимо переменную, объявленную как threadprivate , инициали- зировать значением размножаемой переменной из нити-мастера, то на входе в параллельную область можно использовать опцию copyin . Если значение локальной переменной или переменной, объявленной как threadprivate , необходимо переслать от одной нити всем, работающим в данной параллель- 35 ной области, для этого можно использовать опцию copyprivate директивы single Пример 16 демонстрирует использование опции copyin . Глобальная пере- менная n определена как threadprivate . Применение опции copyin позво- ляет инициализировать локальные копии переменной n начальным значени- ем нити-мастера. Все нити выведут значение n , равное 1 #include int n; #pragma omp threadprivate(n) int main(int argc, char *argv[]) { n=1; #pragma omp parallel copyin(n) { printf(" Значение n: %d\n", n); } } Пример 16a. Опция copyin наязыкеСи. program example16b common/nnn/n integer n !$omp threadprivate(/nnn/) n=1; !$omp parallel copyin(n) print *, " Значение n: ", n !$omp end parallel end Пример 16b. Опция copyin наязыкеФортран. Задания • Может ли одна и та же переменная выступать в одной части програм- мы как общая, а в другой части – как локальная? • Что произойдёт, если несколько нитей одновременно обратятся к об- щей переменной? • Может ли произойти конфликт, если несколько нитей одновременно обратятся к одной и той же локальной переменной? • Каким образом при входе в параллельную область разослать всем по- рождаемым нитям значение некоторой переменной? • Можно ли сохранить значения локальных копий общих переменных после завершения параллельной области? Если да, то что необходимо для их использования? • В чём отличие опции copyin от опции firstprivate ? 36 Распределение работы OpenMP предлагает несколько вариантов распределения работы между за- пущенными нитями. Конструкции распределения работ в OpenMP не порож- дают новых нитей. Низкоуровневое распараллеливание Все нити в параллельной области нумеруются последовательными целыми числами от 0 до N-1 , где N — количество нитей, выполняющих данную об- ласть. Можно программировать на самом низком уровне, распределяя работу с по- мощью функций omp_get_thread_num() и omp_get_num_threads() , воз- вращающих номер нити и общее количество порождённых нитей в текущей параллельной области, соответственно. Вызов функции omp_get_thread_num() позволяет нити получить свой уни- кальный номер в текущей параллельной области. Си: int omp_get_thread_num(void); Фортран: integer function omp_get_thread_num() Вызов функции omp_get_num_threads() позволяет нити получить количест- во нитей в текущей параллельной области. Си: int omp_get_num_threads(void); Фортран: integer function omp_get_num_threads() Пример 17 демонстрирует работу функций omp_get_num_threads() и omp_get_thread_num() . Нить, порядковый номер которой равен 0 , напеча- тает общее количество порождённых нитей, а остальные нити напечатают свой порядковый номер. 37 #include #include int main(int argc, char *argv[]) { int count, num; #pragma omp parallel { count=omp_get_num_threads(); num=omp_get_thread_num(); if (num == 0) printf(" Всего нитей : %d\n", count); else printf(" Нить номер %d\n", num); } } Пример 17a. Функции omp_get_num_threads() и omp_get_thread_num() наязы- кеСи. program example17b include "omp_lib.h" integer count, num !$omp parallel count=omp_get_num_threads() num=omp_get_thread_num() if (num .eq. 0) then print *, " Всего нитей : ", count else print *, " Нить номер ", num end if !$omp end parallel end Пример 17b. Функции omp_get_num_threads() и omp_get_thread_num() наязы- кеФортран. Использование функций omp_get_thread_num() и omp_get_num_threads() позволяет назначать каждой нити свой кусок кода для выполнения, и таким образом распределять работу между нитями в стиле технологии MPI [8]. Од- нако использование этого стиля программирования в OpenMP далеко не все- гда оправдано – программист в этом случае должен явно организовывать синхронизацию доступа к общим данным. Другие способы распределения работ в OpenMP обеспечивают значительную часть этой работы автоматиче- ски. Параллельные циклы Если в параллельной области встретился оператор цикла, то, согласно обще- му правилу, он будет выполнен всеми нитями текущей группы, то есть каж- дая нить выполнит все итерации данного цикла. Для распределения итераций цикла между различными нитями можно использовать директиву |