Здавалка
Главная | Обратная связь

Технология программирования OpenMP. Основные конструкции OpenMP Си реализации.



 

Одним из наиболее популярных методов программирования для компьютеров с общей памятью в настоящее время является технология OpenMP. Стандарт OpenMP разработан для языков Фортран, С и С++, содержит спецификации набора директив компилятора, функций и переменных среды окружения. Для разработки OpenMP программ необходимо подключить файл omp.h

Схематично процесс выполнения OpenMP программ можно представить следующим образом:

За основу берется текст последовательной программы, который разбивается на последовательные и параллельные области. В начальный момент времени порождается нить-мастер или "основная" нить, которая начинает выполнение программы.

В отличие от рассмотренного в реализации MPI термина параллельные процессы, здесь используется термин - нити (threads, легковесные процессы). Поскольку OpenMP – это технология разработки параллельных программ, основанная на использовании общей памяти, она ориентирована на SMP вычислительные системы, где реализован механизм эффективной поддержки нитей, исполняющихся на различных процессорах, что позволяет избежать значительных накладных расходов на поддержку классических UNIX-процессов.

Основная нить исполняет все последовательные области программы. При входе в параллельную область порождаются дополнительные нити, каждая из которых получает свой уникальный номер, причем нить-мастер всегда имеет номер 0. Все нити исполняют один и тот же код, соответствующий параллельной области. При выходе из параллельной области основная нить дожидается завершения остальных нитей, и дальнейшее выполнение программы продолжает только она.

 

Синтаксис OpenMP-директив:

#pragma omp имя_директивы [оператор [оператор] ...]

Все директивы OpenMP используют префикс #pragma omp.

  1. Директива parallel - определяет параллельную секцию

#pragma omp parallel [оператор [оператор] ...]

{

< код параллельной секции >

}

Участок кода, стоящий в фигурных скобках, будет выполняться параллельно. Для выполнения кода параллельной секции, порождается OMP_NUM_THREADS-1 нитей, где OMP_NUM_THREADS - это переменная окружения, значение которой задает пользователь до начала выполнения программы. Все нити исполняют код, заключенный между фигурными скобками. После выхода из параллельной секции автоматически происходит неявная синхронизация порожденных нитей. Когда все порожденные нити доходят до точки выхода из параллельной секции, нить-мастер продолжает выполнение последующей части программы, остальные нити уничтожаются.

 

Параллельные секции могут быть вложенными. По умолчанию вложенная параллельная секция исполняется одной нитью. Принцип обработки вложенных секций определяет переменная OMP_NESTED, значение которой можно изменить с помощью функции OMP_SET_NESTED.

На время выполнения параллельной области, изменить количество порожденных нитей нельзя, однако если пользователь установит значение переменной OMP_DYNAMIC=1, то с помощью функции OMP_SET_NUM_THREADS при входе в следующую параллельную секцию можно изменить значение переменной OMP_NUM_THREADS, а значит и число порождаемых нитей. Значение переменной OMP_DYNAMIC устанавливается функцией OMP_SET_DYNAMIC.

(Клауза) оператор - один из следующих:

  1. if(скалярное_выражение)
  2. private(список)
  3. firstprivate(список)
  4. default(shared | none)
  5. shared(список)
  6. copyin(список)
  7. reduction(оператор: список)

 

1. Если значение выражения в ifистинно, область выполняется параллельно, равно нулю (условие не выполняется), то область parallel выполняется последовательно.

2. private(список) - объявляет перечисленные в списке переменные локальными в каждой нити группы. При входе в параллельную область для каждой локальной переменной в каждой нити создается отдельный экземпляр переменной, доступный только этой нити, который не имеет никакой связи с оригинальной переменной вне параллельной области, т.е. при входе и выходе из параллельной области значение переменной не определено, поэтому при входе требуется инициализация. Изменение нитью значениясвоей локальной переменной, не влияет на изменение значения этой же локальной переменной в других нитях.

3. firstprivate(список) - объявляет перечисленные в списке переменные локальными в каждой нити группы. Локальные копии переменных при входе в параллельную область инициализируются значением оригинальной переменной, при выходе не определены.

4. lastprivate(список) - объявляет перечисленные в списке переменные локальными в каждой нити группы. По окончании параллельно цикла или блока параллельных секций, нить, которая выполнила последнюю итерацию цикла или последнюю секцию блока, обновляет значение оригинальной переменной.

Пример:

………….. // код программы

int i, j;

i =1; j =2;

#pragma omp parallel private(i) firstprivate(j)

{

i =3; // i - private переменная, значение при входе в параллельную область не

// определено, поэтому выполняется инициализация

j =i+j; // j - firstprivate переменная, значение при входе в параллельную область

// определено и равно 2

}

cout<<”i=”<<i<<” j=”<<j<<endl;

………….. // код программы

Переменные i и j при выходе из параллельной области сохранят свои оригинальные значения, т.е. значения до входа в параллельную область i=1, j=2.

Пример:

#pragma omp parallel

{

#pragma omp for lastprivate(i) //значение i не определено при входе,

for (i=0; i<10; i++) // поэтому выполняется инициализация

a[i] = b[i] + 1;

}

a[i-1]=b[i-1];

 

В значение i в конце параллельной области будет равно 10, как в случае последовательного выполнения цикла.

5. default(shared | none) позволяет установить область видимости переменных. По умолчанию устанавливается shared.

Использованиеdefault(none) требует, чтобы для каждой переменной в параллельной секции была явно задана область видимости. В директиве parallel может быть определен единственный оператор default.

Переменные могут быть исключены из умалчиваемого определения путем использования директив private, firstprivate, lastprivate, reduction и shared, например:

#pragma omp parallel for default(shared) firstprivate(i) private(x) private(r) lastprivate(i)

6. shared(список) - объявляет перечисленные в списке переменные общими для всех нитей группы. Т.е. каждая общая переменная существует в одном экземпляре для всей программы и доступна для каждой нити под одним и тем же именем.

 

Пример: Каждая нить в параллельной области принимает решение, основываясь на номере нити, какую часть массива x обрабатывать.

#pragma omp parallel shared(x, npoints) private(iam, np, ipoints)

{

iam = omp_get_thread_num();

np = omp_get_num_threads();

ipoints = npoints / np;

subdomain(x, iam, ipoints); //функция обработки массиваx

}

7. copyin(список) - выполняет присвоение одного и того же значения переменной, определенной в threadprivate в каждую нить параллельной области. Т.е. для каждой переменной, указанной в операторе copyin, значение переменной из основной нити при воде в параллельную область копируется в приватные копии переменных каждой нити. Ограничения, накладываемые на использование клаузы copyinследующее -переменные, указанные в copyin, должны быть переменными threadprivate.

8. reduction(операция: список) - выполняет редукцию переменных, которые перечислены в списке, с оператором op. Список переменных перечисляется через запятую.

Допустимые операции: +, -, *, /, &, |, ||, &&

Ограничения, накладываемые на использование директивы reduction:

1. Тип переменных должен быть приемлемым для операции редукции, не разрешены типы указателя и ссылки.

2.Переменные, которые указаны в клаузе reduction, не должны быть константами.

3.Переменные, которые указаны в клаузе reduction, должны быть разделяемыми в общем контексте программы.

Пример: В данном примере каждая нить имеет свою копию переменной редукции: a и y. После окончания работы выполняется операция редуцирования (в данном случае сложение) всех локальных копий переменных.

#pragma omp parallel for reduction(+: a, y)

for (i=0; i<n; i++) {

a += b[i];

y = sum(y, c[i]);

}







©2015 arhivinfo.ru Все права принадлежат авторам размещенных материалов.