С. антоновпараллельноепрограммированиесиспользованиемтехнологииopenMP
Скачать 0.55 Mb.
|
for ( do ... [end do] ). 38 Си: #pragma omp for [ опция [[,] опция ]...] Фортран: !$omp do [ опция [[,] опция ]...] < блок циклов do> [!$omp end do [nowait]] Эта директива относится к идущему следом за данной директивой блоку, включающему операторы for ( do ). Возможные опции: private( список ) – задаёт список переменных, для которых порожда- ется локальная копия в каждой нити; начальное значение локальных копий переменных из списка не определено; firstprivate( список ) – задаёт список переменных, для которых по- рождается локальная копия в каждой нити; локальные копии перемен- ных инициализируются значениями этих переменных в нити-мастере; lastprivate( список ) – переменным, перечисленным в списке, при- сваивается результат с последнего витка цикла; reduction( оператор : список ) – задаёт оператор и список общих пе- ременных; для каждой переменной создаются локальные копии в каж- дой нити; локальные копии инициализируются соответственно типу оператора (для аддитивных операций – 0 или его аналоги, для мульти- пликативных операций – 1 или её аналоги); над локальными копиями переменных после завершения всех итераций цикла выполняется за- данный оператор; оператор это: для языка Си – + , * , - , & , | , ^ , && , || , для языка Фортран – + , * , - , .and. , .or. , .eqv. , .neqv. , max , min , iand , ior , ieor ; порядок выполнения операторов не определён, поэто- му результат может отличаться от запуска к запуску; schedule(type[, chunk]) – опция задаёт, каким образом итерации цикла распределяются между нитями; collapse(n) — опция указывает, что n последовательных тесновло- женных циклов ассоциируется с данной директивой; для циклов обра- зуется общее пространство итераций, которое делится между нитями; если опция collapse не задана, то директива относится только к одно- му непосредственно следующему за ней циклу; 39 ordered – опция, говорящая о том, что в цикле могут встречаться ди- рективы ordered ; в этом случае определяется блок внутри тела цикла, который должен выполняться в том порядке, в котором итерации идут в последовательном цикле; nowait – в конце параллельного цикла происходит неявная барьерная синхронизация параллельно работающих нитей: их дальнейшее выпол- нение происходит только тогда, когда все они достигнут данной точки; если в подобной задержке нет необходимости, опция nowait позволяет нитям, уже дошедшим до конца цикла, продолжить выполнение без синхронизации с остальными. Если директива end do в явном виде не указана, то в конце параллельного цикла синхронизация все равно бу- дет выполнена. Если в программе на языке Фортран не указывается директива end do , то она предполагается в конце цикла do На вид параллельных циклов накладываются достаточно жёсткие ограниче- ния. В частности, предполагается, что корректная программа не должна зави- сеть от того, какая именно нить какую итерацию параллельного цикла вы- полнит. Нельзя использовать побочный выход из параллельного цикла. Размер блока итераций, указанный в опции schedule , не должен изменяться в рамках цикла. Формат параллельных циклов на языке Си упрощённо можно представить следующим образом: for([ целочисленный тип ] i = инвариант цикла ; i {<,>,=,<=,>=} инвариант цикла ; i {+,-}= инвариант цикла ) Эти требования введены для того, чтобы OpenMP мог при входе в цикл точно определить число итераций. Если директива параллельного выполнения стоит перед гнездом циклов, за- вершающихся одним оператором, то директива действует только на самый внешний цикл. Итеративная переменная распределяемого цикла по смыслу должна быть ло- кальной, поэтому в случае, если она специфицирована общей, то она неявно делается локальной при входе в цикл. После завершения цикла значение ите- ративной переменной цикла не определено, если она не указана в опции lastprivate 40 Пример 18 демонстрирует использование директивы for ( do ). В последова- тельной области инициализируются три исходных массива A , B , C . В парал- лельной области данные массивы объявлены общими. Вспомогательные пе- ременные i и n объявлены локальными. Каждая нить присвоит переменной n свой порядковый номер. Далее с помощью директивы for ( do ) определяется цикл, итерации которого будут распределены между существующими нитя- ми. На каждой i -ой итерации данный цикл сложит i -ые элементы массивов A и B и результат запишет в i -ый элемент массива C . Также на каждой итера- ции будет напечатан номер нити, выполнившей данную итерацию. #include #include int main(int argc, char *argv[]) { int A[10], B[10], C[10], i, n; /* Заполним исходные массивы */ for (i=0; i<10; i++){ A[i]=i; B[i]=2*i; C[i]=0; } #pragma omp parallel shared(A, B, C) private(i, n) { /* Получим номер текущей нити */ n=omp_get_thread_num(); #pragma omp for for (i=0; i<10; i++) { C[i]=A[i]+B[i]; printf(" Нить %d сложила элементы с номером %d\n", n, i); } } } Пример 18a. Директива for наязыкеСи. 41 program example18b include "omp_lib.h" integer A(10), B(10), C(10), i, n C Заполним исходные массивы do i=1, 10 A(i)=i B(i)=2*i C(i)=0 end do !$omp parallel shared(A, B, C) private(i, n) C Получим номер текущей нити n=omp_get_thread_num() !$omp do do i=1, 10 C(i)=A(i)+B(i) print *, " Нить ", n, " сложила элементы с номером ", i end do !$omp end parallel end Пример 18b. Директива do наязыкеФортран. В опции schedule параметр type задаёт следующий тип распределения ите- раций: • static – блочно-циклическое распределение итераций цикла; размер блока – chunk . Первый блок из chunk итераций выполняет нулевая нить, второй блок — следующая и т.д. до последней нити, затем рас- пределение снова начинается с нулевой нити. Если значение chunk не указано, то всё множество итераций делится на непрерывные куски примерно одинакового размера (конкретный способ зависит от реали- зации), и полученные порции итераций распределяются между нитями. • dynamic – динамическое распределение итераций с фиксированным размером блока: сначала каждая нить получает chunk итераций (по умолчанию chunk=1 ), та нить, которая заканчивает выполнение своей порции итераций, получает первую свободную порцию из chunk ите- раций. Освободившиеся нити получают новые порции итераций до тех пор, пока все порции не будут исчерпаны. Последняя порция может содержать меньше итераций, чем все остальные. • guided – динамическое распределение итераций, при котором размер порции уменьшается с некоторого начального значения до величины chunk (по умолчанию chunk=1 ) пропорционально количеству ещё не распределённых итераций, делённому на количество нитей, выпол- няющих цикл. Размер первоначально выделяемого блока зависит от реализации. В ряде случаев такое распределение позволяет аккуратнее 42 разделить работу и сбалансировать загрузку нитей. Количество итера- ций в последней порции может оказаться меньше значения chunk • auto – способ распределения итераций выбирается компилятором и/или системой выполнения. Параметр chunk при этом не задаётся. • runtime – способ распределения итераций выбирается во время работы программы по значению переменной среды OMP_SCHEDULE . Параметр chunk при этом не задаётся. Пример 19 демонстрирует использование опции schedule с параметрами (static) , (static, 1) , (static, 2) , (dynamic) , (dynamic, 2) , (guided) , (guided, 2) . В параллельной области выполняется цикл, итерации которого будут распределены между существующими нитями. На каждой итерации будет напечатано, какая нить выполнила данную итерацию. В тело цикла вставлена также задержка, имитирующая некоторые вычисления. #include #include int main(int argc, char *argv[]) { int i; #pragma omp parallel private(i) { #pragma omp for schedule (static) //#pragma omp for schedule (static, 1) //#pragma omp for schedule (static, 2) //#pragma omp for schedule (dynamic) //#pragma omp for schedule (dynamic, 2) //#pragma omp for schedule (guided) //#pragma omp for schedule (guided, 2) for (i=0; i<10; i++) { printf(" Нить %d выполнила итерацию %d\n", omp_get_thread_num(), i); sleep(1); } } } Пример 19a. Опция schedule наязыкеСи. 43 program example19b include "omp_lib.h" integer i !$omp parallel private(i) !$omp do schedule (static) C!$omp do schedule (static, 1) C!$omp do schedule (static, 2) C!$omp do schedule (dynamic) C!$omp do schedule (dynamic, 2) C!$omp do for schedule (guided) C!$omp do for schedule (guided, 2) do i=0, 9 print *, " Нить ", omp_get_thread_num(), & " выполнила итерацию ", i call sleep(1) end do !$omp end parallel end Пример 19b. Опция schedule наязыкеФортран. Результаты выполнения примера 19 с различными типами распределения итераций приведены в таблице 1. Столбцы соответствуют различным типам распределений, а строки – номеру итерации. В ячейках таблицы указаны но- мера нити, выполнявшей соответствующую итерацию. Во всех случаях для выполнения параллельного цикла использовалось 4 нити. Для динамических способов распределения итераций ( dynamic , guided ) конкретное распреде- ление между нитями может отличаться от запуска к запуску. i static static, 1 static, 2 dynamic dynamic, 2 guided guided, 2 0 0 0 0 0 0 0 0 1 0 1 0 1 0 2 0 2 0 2 1 2 1 1 1 3 1 3 1 3 1 3 1 4 1 0 2 1 2 1 2 5 1 1 2 3 2 2 2 6 2 2 3 2 3 3 3 7 2 3 3 0 3 0 3 8 3 0 0 1 3 0 0 9 3 1 0 0 3 3 0 Таблица 1. Распределениеитерацийпонитям. 44 В таблице 1 видна разница между распределением итераций при использова- нии различных вариантов. К наибольшему дисбалансу привели варианты распределения (static, 2) , (dynamic, 2) и (guided, 2) . Во всех этих случаях одной из нитей достаётся на две итерации больше, чем остальным. В других случаях эта разница несколько сглаживается. Пример 20 демонстрирует использование опции schedule с параметрами (static, 6) , (dynamic, 6) , (guided, 6) . В параллельной области выпол- няется цикл, итерации которого будут распределены между существующими нитями. На каждой итерации будет напечатано, какая нить выполнила дан- ную итерацию. В тело цикла вставлена также задержка, имитирующая неко- торые вычисления. #include #include int main(int argc, char *argv[]) { int i; #pragma omp parallel private(i) { #pragma omp for schedule (static, 6) //#pragma omp for schedule (dynamic, 6) //#pragma omp for schedule (guided, 6) for (i=0; i<200; i++) { printf(" Нить %d выполнила итерацию %d\n", omp_get_thread_num(), i); sleep(1); } } } Пример 20a. Опция schedule наязыкеСи. program example20b include "omp_lib.h" integer i !$omp parallel private(i) !$omp do schedule (static, 6) C!$omp do schedule (dynamic, 6) C!$omp do schedule (guided, 6) do i=0, 200 print *, " Нить ", omp_get_thread_num(), & " выполнила итерацию ", i call sleep(1) end do !$omp end parallel end Пример 20b. Опция schedule наязыкеФортран. 45 В результате выполнения примера 20 с тремя разными вариантами директи- вы for получаются следующие распределения итераций (рис. 1a – рис. 1c). Рис.1a. Распределениеитерацийпонитямдля (static, 6) Рис.1b. Распределениеитерацийпонитямдля (dynamic, 6) 46 Рис.1c. Распределениеитерацийпонитямдля (guided, 6) На рисунках видна регулярность распределения порций по 6 итераций при указании (static, 6) , более динамичная картина распределения таких же порций при указании (dynamic, 6) и распределение уменьшающимися пор- циями при указании (guided, 6) . В последнем случае размер порций уменьшался с 24 в самом начале цикла до 6 в конце. Значение по умолчанию переменной OMP_SCHEDULE зависит от реализации. Если переменная задана неправильно, то поведение программы при задании опции runtime также зависит от реализации. Задать значение переменной OMP_SCHEDULE в Linux в командной оболочке bash можно при помощи команды следующего вида: export OMP_SCHEDULE="dynamic,1" Изменить значение переменной OMP_SCHEDULE из программы можно с помо- щью вызова функции omp_set_schedule() Си: void omp_set_schedule(omp_sched_t type, int chunk); 47 Фортран: subroutine omp_set_schedule(type, chunk) integer (kind=omp_sched_kind) type integer chunk Допустимые значения констант описаны в файле omp.h ( omp_lib.h ). Как минимум, они должны включать для языка Си следующие варианты: typedef enum omp_sched_t { omp_sched_static = 1, omp_sched_dynamic = 2, omp_sched_guided = 3, omp_sched_auto = 4 } omp_sched_t; Для языка Фортран должны быть заданы как минимум следующие варианты: integer(kind=omp_sched_kind), parameter :: omp_sched_static = 1 integer(kind=omp_sched_kind), parameter :: omp_sched_dynamic = 2 integer(kind=omp_sched_kind), parameter :: omp_sched_guided = 3 integer(kind=omp_sched_kind), parameter :: omp_sched_auto = 4 При помощи вызова функции omp_get_schedule() пользователь может уз- нать текущее значение переменной OMP_SCHEDULE Си: void omp_get_schedule(omp_sched_t* type, int* chunk); Фортран: subroutine omp_get_schedule(type, chunk); integer (kind=omp_sched_kind) type integer chunk При распараллеливании цикла программист должен убедиться в том, что итерации данного цикла не имеют информационных зависимостей [5]. Если цикл не содержит зависимостей, его итерации можно выполнять в любом по- рядке, в том числе параллельно. Соблюдение этого важного требования ком- пилятор не проверяет, вся ответственность лежит на программисте. Если дать указание компилятору распараллелить цикл, содержащий зависимости, ком- пилятор это сделает, но результат работы программы может оказаться некор- ректным. Параллельные секции Директива sections ( sections ... end sections ) используется для зада- ния конечного (неитеративного) параллелизма. Си: |